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/01/08 14:17:21 UTC

[01/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Repository: syncope
Updated Branches:
  refs/heads/2_0_X fc8761c33 -> 99369c311


http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/MappingUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/MappingUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/MappingUtil.java
new file mode 100644
index 0000000..cb2e140
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/MappingUtil.java
@@ -0,0 +1,736 @@
+/*
+ * 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.server.utils;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.persistence.api.entity.membership.MDerSchema;
+import org.apache.syncope.persistence.api.entity.membership.MPlainSchema;
+import org.apache.syncope.persistence.api.entity.membership.MVirSchema;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerSchema;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.role.RPlainSchema;
+import org.apache.syncope.persistence.api.entity.role.RVirSchema;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.server.security.Encryptor;
+import org.apache.syncope.server.spring.ApplicationContextProvider;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.identityconnectors.framework.common.FrameworkUtil;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public final class MappingUtil {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(MappingUtil.class);
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    public static <T extends MappingItem> List<T> getMatchingMappingItems(
+            final Collection<T> items, final IntMappingType type) {
+
+        final List<T> result = new ArrayList<>();
+
+        for (T mapItem : items) {
+            if (mapItem.getIntMappingType() == type) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    public static <T extends MappingItem> List<T> getMatchingMappingItems(final Collection<T> items,
+            final String intAttrName, final IntMappingType type) {
+
+        final List<T> result = new ArrayList<>();
+
+        for (T mapItem : items) {
+            if (mapItem.getIntMappingType() == type && intAttrName.equals(mapItem.getIntAttrName())) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    public static <T extends MappingItem> Set<T> getMatchingMappingItems(final Collection<T> mapItems,
+            final String intAttrName) {
+
+        final Set<T> result = new HashSet<>();
+
+        for (T mapItem : mapItems) {
+            if (intAttrName.equals(mapItem.getIntAttrName())) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare attributes for sending to a connector instance.
+     *
+     * @param attrUtil user / role
+     * @param subject given user / role
+     * @param password clear-text password
+     * @param changePwd whether password should be included for propagation attributes or not
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param membVAttrsToBeRemoved membership virtual attributes to be removed
+     * @param membVAttrsToBeUpdated membership virtual attributes to be added
+     * @param enable whether user must be enabled or not
+     * @param resource target resource
+     * @return account link + prepared attributes
+     */
+    public static Map.Entry<String, Set<Attribute>> prepareAttributes(
+            final AttributableUtil attrUtil, final Subject<?, ?, ?> subject,
+            final String password,
+            final boolean changePwd,
+            final Set<String> vAttrsToBeRemoved,
+            final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved,
+            final Map<String, AttrMod> membVAttrsToBeUpdated,
+            final Boolean enable,
+            final ExternalResource resource) {
+
+        LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}",
+                subject, resource, subject.getPlainAttrs());
+
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final VirAttrCache virAttrCache = context.getBean(VirAttrCache.class);
+        final PasswordGenerator passwordGenerator = context.getBean(PasswordGenerator.class);
+
+        Set<Attribute> attributes = new HashSet<>();
+        String accountId = null;
+
+        for (MappingItem mapping : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+            LOG.debug("Processing schema {}", mapping.getIntAttrName());
+
+            try {
+                if ((attrUtil.getType() == AttributableType.USER
+                        && mapping.getIntMappingType() == IntMappingType.UserVirtualSchema)
+                        || (attrUtil.getType() == AttributableType.ROLE
+                        && mapping.getIntMappingType() == IntMappingType.RoleVirtualSchema)) {
+
+                    LOG.debug("Expire entry cache {}-{}", subject.getKey(), mapping.getIntAttrName());
+                    virAttrCache.expire(attrUtil.getType(), subject.getKey(), mapping.getIntAttrName());
+                }
+
+                // SYNCOPE-458 expire cache also for membership virtual schemas
+                if (attrUtil.getType() == AttributableType.USER && mapping.getIntMappingType()
+                        == IntMappingType.MembershipVirtualSchema && (subject instanceof User)) {
+
+                    final User user = (User) subject;
+                    for (Membership membership : user.getMemberships()) {
+                        LOG.debug("Expire entry cache {}-{} for membership {}", subject.getKey(),
+                                mapping.getIntAttrName(), membership);
+                        virAttrCache.expire(AttributableType.MEMBERSHIP, membership.getKey(),
+                                mapping.getIntAttrName());
+                    }
+                }
+
+                Map.Entry<String, Attribute> preparedAttr = prepareAttr(
+                        resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated,
+                        membVAttrsToBeRemoved, membVAttrsToBeUpdated);
+
+                if (preparedAttr != null && preparedAttr.getKey() != null) {
+                    accountId = preparedAttr.getKey();
+                }
+
+                if (preparedAttr != null && preparedAttr.getValue() != null) {
+                    Attribute alreadyAdded = AttributeUtil.find(preparedAttr.getValue().getName(), attributes);
+
+                    if (alreadyAdded == null) {
+                        attributes.add(preparedAttr.getValue());
+                    } else {
+                        attributes.remove(alreadyAdded);
+
+                        Set<Object> values = new HashSet<>(alreadyAdded.getValue());
+                        values.addAll(preparedAttr.getValue().getValue());
+
+                        attributes.add(AttributeBuilder.build(preparedAttr.getValue().getName(), values));
+                    }
+                }
+            } catch (Exception e) {
+                LOG.debug("Attribute '{}' processing failed", mapping.getIntAttrName(), e);
+            }
+        }
+
+        final Attribute accountIdExtAttr =
+                AttributeUtil.find(attrUtil.getAccountIdItem(resource).getExtAttrName(), attributes);
+        if (accountIdExtAttr != null) {
+            attributes.remove(accountIdExtAttr);
+            attributes.add(AttributeBuilder.build(attrUtil.getAccountIdItem(resource).getExtAttrName(), accountId));
+        }
+        attributes.add(MappingUtil.evaluateNAME(subject, resource, accountId));
+
+        if (enable != null) {
+            attributes.add(AttributeBuilder.buildEnabled(enable));
+        }
+        if (!changePwd) {
+            Attribute pwdAttr = AttributeUtil.find(OperationalAttributes.PASSWORD_NAME, attributes);
+            if (pwdAttr != null) {
+                attributes.remove(pwdAttr);
+            }
+        }
+
+        return new AbstractMap.SimpleEntry<>(accountId, attributes);
+    }
+
+    /**
+     * Prepare an attribute to be sent to a connector instance.
+     *
+     * @param resource target resource
+     * @param mapItem mapping item for the given attribute
+     * @param subject given user
+     * @param password clear-text password
+     * @param passwordGenerator password generator
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @return account link + prepared attribute
+     */
+    @SuppressWarnings("unchecked")
+    private static Map.Entry<String, Attribute> prepareAttr(
+            final ExternalResource resource, final MappingItem mapItem,
+            final Subject<?, ?, ?> subject, final String password, final PasswordGenerator passwordGenerator,
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) {
+
+        final List<Attributable<?, ?, ?>> attributables = new ArrayList<>();
+
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final AttributableUtilFactory attrUtilFactory = context.getBean(AttributableUtilFactory.class);
+        final ConnObjectUtil connObjectUtil = context.getBean(ConnObjectUtil.class);
+
+        switch (mapItem.getIntMappingType().getAttributableType()) {
+            case USER:
+                if (subject instanceof User) {
+                    attributables.add(subject);
+                }
+                break;
+
+            case ROLE:
+                if (subject instanceof User) {
+                    for (Role role : ((User) subject).getRoles()) {
+                        connObjectUtil.retrieveVirAttrValues(role, attrUtilFactory.getInstance(role));
+                        attributables.add(role);
+                    }
+                }
+                if (subject instanceof Role) {
+                    attributables.add(subject);
+                }
+                break;
+
+            case MEMBERSHIP:
+                if (subject instanceof User) {
+                    attributables.addAll(((User) subject).getMemberships());
+                }
+                break;
+
+            default:
+        }
+
+        List<PlainAttrValue> values = getIntValues(
+                resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
+                membVAttrsToBeUpdated);
+
+        PlainSchema schema = null;
+        boolean readOnlyVirSchema = false;
+        AttrSchemaType schemaType;
+        final Map.Entry<String, Attribute> result;
+
+        switch (mapItem.getIntMappingType()) {
+            case UserSchema:
+            case RoleSchema:
+            case MembershipSchema:
+                final PlainSchemaDAO plainSchemaDAO = context.getBean(PlainSchemaDAO.class);
+                schema = plainSchemaDAO.find(mapItem.getIntAttrName(),
+                        MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType()));
+                schemaType = schema == null ? AttrSchemaType.String : schema.getType();
+                break;
+
+            case UserVirtualSchema:
+            case RoleVirtualSchema:
+            case MembershipVirtualSchema:
+                VirSchemaDAO virSchemaDAO = context.getBean(VirSchemaDAO.class);
+                VirSchema virSchema = virSchemaDAO.find(mapItem.getIntAttrName(),
+                        MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType()));
+                readOnlyVirSchema = (virSchema != null && virSchema.isReadonly());
+                schemaType = AttrSchemaType.String;
+                break;
+
+            default:
+                schemaType = AttrSchemaType.String;
+        }
+
+        final String extAttrName = mapItem.getExtAttrName();
+
+        LOG.debug("Define mapping for: "
+                + "\n* ExtAttrName " + extAttrName
+                + "\n* is accountId " + mapItem.isAccountid()
+                + "\n* is password " + (mapItem.isPassword() || mapItem.getIntMappingType() == IntMappingType.Password)
+                + "\n* mandatory condition " + mapItem.getMandatoryCondition()
+                + "\n* Schema " + mapItem.getIntAttrName()
+                + "\n* IntMappingType " + mapItem.getIntMappingType().toString()
+                + "\n* ClassType " + schemaType.getType().getName()
+                + "\n* Values " + values);
+
+        if (readOnlyVirSchema) {
+            result = null;
+        } else {
+            final List<Object> objValues = new ArrayList<>();
+
+            for (PlainAttrValue value : values) {
+                if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) {
+                    objValues.add(value.getValue());
+                } else {
+                    objValues.add(value.getValueAsString());
+                }
+            }
+
+            if (mapItem.isAccountid()) {
+                result = new AbstractMap.SimpleEntry<>(objValues.iterator().next().toString(), null);
+            } else if (mapItem.isPassword() && subject instanceof User) {
+                String passwordAttrValue = password;
+                if (StringUtils.isBlank(passwordAttrValue)) {
+                    User user = (User) subject;
+                    if (user.canDecodePassword()) {
+                        try {
+                            passwordAttrValue = ENCRYPTOR.decode(user.getPassword(), user.getCipherAlgorithm());
+                        } catch (Exception e) {
+                            LOG.error("Could not decode password for {}", user, e);
+                        }
+                    } else if (resource.isRandomPwdIfNotProvided()) {
+                        try {
+                            passwordAttrValue = passwordGenerator.generate(user);
+                        } catch (InvalidPasswordPolicySpecException e) {
+                            LOG.error("Could not generate policy-compliant random password for {}", user, e);
+                        }
+                    }
+                }
+
+                if (passwordAttrValue == null) {
+                    result = null;
+                } else {
+                    result = new AbstractMap.SimpleEntry<>(
+                            null,
+                            AttributeBuilder.buildPassword(passwordAttrValue.toCharArray()));
+                }
+            } else {
+                if ((schema != null && schema.isMultivalue()) || attrUtilFactory.getInstance(subject).getType()
+                        != mapItem.getIntMappingType().getAttributableType()) {
+
+                    result = new AbstractMap.SimpleEntry<>(
+                            null,
+                            AttributeBuilder.build(extAttrName, objValues));
+                } else {
+                    result = new AbstractMap.SimpleEntry<>(
+                            null, objValues.isEmpty()
+                                    ? AttributeBuilder.build(extAttrName)
+                                    : AttributeBuilder.build(extAttrName, objValues.iterator().next()));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Build __NAME__ for propagation. First look if there ia a defined accountLink for the given resource (and in this
+     * case evaluate as JEXL); otherwise, take given accountId.
+     *
+     * @param subject given user / role
+     * @param resource target resource
+     * @param accountId accountId
+     * @return the value to be propagated as __NAME__
+     */
+    public static Name evaluateNAME(final Subject<?, ?, ?> subject,
+            final ExternalResource resource, final String accountId) {
+
+        final AttributableUtilFactory attrUtilFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class);
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
+
+        if (StringUtils.isBlank(accountId)) {
+            // LOG error but avoid to throw exception: leave it to the external resource
+            LOG.error("Missing accountId for '{}': ", resource.getKey());
+        }
+
+        // Evaluate AccountLink expression
+        String evalAccountLink = null;
+        if (StringUtils.isNotBlank(attrUtil.getAccountLink(resource))) {
+            final JexlContext jexlContext = new MapContext();
+            JexlUtil.addFieldsToContext(subject, jexlContext);
+            JexlUtil.addAttrsToContext(subject.getPlainAttrs(), jexlContext);
+            JexlUtil.addDerAttrsToContext(subject.getDerAttrs(), subject.getPlainAttrs(), jexlContext);
+            evalAccountLink = JexlUtil.evaluate(attrUtil.getAccountLink(resource), jexlContext);
+        }
+
+        // If AccountLink evaluates to an empty string, just use the provided AccountId as Name(),
+        // otherwise evaluated AccountLink expression is taken as Name().
+        Name name;
+        if (StringUtils.isBlank(evalAccountLink)) {
+            // add AccountId as __NAME__ attribute ...
+            LOG.debug("Add AccountId [{}] as __NAME__", accountId);
+            name = new Name(accountId);
+        } else {
+            LOG.debug("Add AccountLink [{}] as __NAME__", evalAccountLink);
+            name = new Name(evalAccountLink);
+
+            // AccountId not propagated: it will be used to set the value for __UID__ attribute
+            LOG.debug("AccountId will be used just as __UID__ attribute");
+        }
+
+        return name;
+    }
+
+    private static String getRoleOwnerValue(
+            final ExternalResource resource, final Subject<?, ?, ?> subject) {
+
+        AttributableUtilFactory attrUtilFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class);
+
+        Map.Entry<String, Attribute> preparedAttr = prepareAttr(
+                resource, attrUtilFactory.getInstance(subject).getAccountIdItem(resource), subject, null, null,
+                Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap(),
+                Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap());
+        String accountId = preparedAttr.getKey();
+
+        final Name roleOwnerName = evaluateNAME(subject, resource, accountId);
+        return roleOwnerName.getNameValue();
+    }
+
+    /**
+     * Get attribute values.
+     *
+     * @param resource target resource
+     * @param mappingItem mapping item
+     * @param attributables list of attributables
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param membVAttrsToBeRemoved membership virtual attributes to be removed
+     * @param membVAttrsToBeUpdated membership virtual attributes to be added
+     * @return attribute values.
+     */
+    public static List<PlainAttrValue> getIntValues(final ExternalResource resource,
+            final MappingItem mappingItem, final List<Attributable<?, ?, ?>> attributables,
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) {
+
+        LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mappingItem.getIntMappingType());
+
+        final EntityFactory entityFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(EntityFactory.class);
+        List<PlainAttrValue> values = new ArrayList<>();
+        PlainAttrValue attrValue;
+        switch (mappingItem.getIntMappingType()) {
+            case UserSchema:
+            case RoleSchema:
+            case MembershipSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    final PlainAttr attr = attributable.getPlainAttr(mappingItem.getIntAttrName());
+                    if (attr != null) {
+                        if (attr.getUniqueValue() != null) {
+                            values.add(attr.getUniqueValue());
+                        } else if (attr.getValues() != null) {
+                            values.addAll(attr.getValues());
+                        }
+                    }
+
+                    LOG.debug("Retrieved attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            attr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+
+                break;
+
+            case UserVirtualSchema:
+            case RoleVirtualSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    VirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName());
+                    if (virAttr != null) {
+                        if (vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) {
+                            if (vAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                                virAttr.getValues().addAll(
+                                        vAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded());
+                            } else if (vAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                            } else {
+                                throw new IllegalArgumentException("Don't need to update virtual attribute '"
+                                        + mappingItem.getIntAttrName() + "'");
+                            }
+                        }
+                        if (virAttr.getValues() != null) {
+                            for (String value : virAttr.getValues()) {
+                                attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                                attrValue.setStringValue(value);
+                                values.add(attrValue);
+                            }
+                        }
+                    }
+
+                    LOG.debug("Retrieved {} virtual attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            attributable.getClass().getSimpleName(),
+                            virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case MembershipVirtualSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    VirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName());
+                    if (virAttr != null) {
+                        if (membVAttrsToBeRemoved != null && membVAttrsToBeUpdated != null) {
+                            if (membVAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                                virAttr.getValues().addAll(
+                                        membVAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded());
+                            } else if (membVAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                            } else {
+                                throw new IllegalArgumentException("Don't need to update membership virtual attribute '"
+                                        + mappingItem.getIntAttrName() + "'");
+                            }
+                        }
+                        if (virAttr.getValues() != null) {
+                            for (String value : virAttr.getValues()) {
+                                attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                                attrValue.setStringValue(value);
+                                values.add(attrValue);
+                            }
+                        }
+                    }
+
+                    LOG.debug("Retrieved {} virtual attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            attributable.getClass().getSimpleName(),
+                            virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case UserDerivedSchema:
+            case RoleDerivedSchema:
+            case MembershipDerivedSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    DerAttr derAttr = attributable.getDerAttr(mappingItem.getIntAttrName());
+                    if (derAttr != null) {
+                        attrValue = attributable instanceof Role
+                                ? entityFactory.newEntity(RPlainAttrValue.class)
+                                : entityFactory.newEntity(UPlainAttrValue.class);
+                        attrValue.setStringValue(derAttr.getValue(attributable.getPlainAttrs()));
+                        values.add(attrValue);
+                    }
+
+                    LOG.debug("Retrieved attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            derAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case UserId:
+            case RoleId:
+            case MembershipId:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                    attrValue.setStringValue(attributable.getKey().toString());
+                    values.add(attrValue);
+                }
+                break;
+
+            case Username:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof User) {
+                        attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                        attrValue.setStringValue(((User) attributable).getUsername());
+                        values.add(attrValue);
+                    }
+                }
+                break;
+
+            case RoleName:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof Role) {
+                        attrValue = entityFactory.newEntity(RPlainAttrValue.class);
+                        attrValue.setStringValue(((Role) attributable).getName());
+                        values.add(attrValue);
+                    }
+                }
+                break;
+
+            case RoleOwnerSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof Role) {
+                        Role role = (Role) attributable;
+                        String roleOwnerValue = null;
+                        if (role.getUserOwner() != null && resource.getUmapping() != null) {
+                            roleOwnerValue = getRoleOwnerValue(resource, role.getUserOwner());
+                        }
+                        if (role.getRoleOwner() != null && resource.getRmapping() != null) {
+                            roleOwnerValue = getRoleOwnerValue(resource, role.getRoleOwner());
+                        }
+
+                        if (StringUtils.isNotBlank(roleOwnerValue)) {
+                            attrValue = entityFactory.newEntity(RPlainAttrValue.class);
+                            attrValue.setStringValue(roleOwnerValue);
+                            values.add(attrValue);
+                        }
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        LOG.debug("Retrieved values '{}'", values);
+
+        return values;
+    }
+
+    /**
+     * Get accountId internal value.
+     *
+     * @param attributable attributable
+     * @param accountIdItem accountId mapping item
+     * @param resource external resource
+     * @return accountId internal value
+     */
+    public static String getAccountIdValue(final Attributable<?, ?, ?> attributable,
+            final ExternalResource resource, final MappingItem accountIdItem) {
+
+        List<PlainAttrValue> values = getIntValues(resource, accountIdItem,
+                Collections.<Attributable<?, ?, ?>>singletonList(attributable), null, null, null, null);
+        return values == null || values.isEmpty()
+                ? null
+                : values.get(0).getValueAsString();
+    }
+
+    /**
+     * For given source mapping type, return the corresponding Class object.
+     *
+     * @param intMappingType source mapping type
+     * @return corresponding Class object, if any (can be null)
+     */
+    @SuppressWarnings("rawtypes")
+    public static Class getIntMappingTypeClass(final IntMappingType intMappingType) {
+        Class result;
+
+        switch (intMappingType) {
+            case UserSchema:
+                result = UPlainSchema.class;
+                break;
+
+            case RoleSchema:
+                result = RPlainSchema.class;
+                break;
+
+            case MembershipSchema:
+                result = MPlainSchema.class;
+                break;
+
+            case UserDerivedSchema:
+                result = UDerSchema.class;
+                break;
+
+            case RoleDerivedSchema:
+                result = RDerSchema.class;
+                break;
+
+            case MembershipDerivedSchema:
+                result = MDerSchema.class;
+                break;
+
+            case UserVirtualSchema:
+                result = UVirSchema.class;
+                break;
+
+            case RoleVirtualSchema:
+                result = RVirSchema.class;
+                break;
+
+            case MembershipVirtualSchema:
+                result = MVirSchema.class;
+                break;
+
+            default:
+                result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private MappingUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/PasswordGenerator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/PasswordGenerator.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/PasswordGenerator.java
new file mode 100644
index 0000000..9ad4ad0
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/PasswordGenerator.java
@@ -0,0 +1,320 @@
+/*
+ * 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.server.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.server.utils.policy.PolicyPattern;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Generate random passwords according to given policies.
+ *
+ * @see PasswordPolicy
+ */
+@Component
+public class PasswordGenerator {
+
+    private static final char[] SPECIAL_CHARS = { '!', '£', '%', '&', '(', ')', '?', '#', '$' };
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    public String generate(final List<PasswordPolicySpec> ppSpecs)
+            throws InvalidPasswordPolicySpecException {
+
+        PasswordPolicySpec policySpec = merge(ppSpecs);
+
+        check(policySpec);
+
+        return generate(policySpec);
+    }
+
+    public String generate(final User user)
+            throws InvalidPasswordPolicySpecException {
+
+        List<PasswordPolicySpec> ppSpecs = new ArrayList<PasswordPolicySpec>();
+
+        PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy();
+        if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) {
+            ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class));
+        }
+
+        for (Role role : user.getRoles()) {
+            if (role.getPasswordPolicy() != null
+                    && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+            }
+        }
+
+        for (ExternalResource resource : user.getResources()) {
+            if (resource.getPasswordPolicy() != null
+                    && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+            }
+        }
+
+        PasswordPolicySpec policySpec = merge(ppSpecs);
+        check(policySpec);
+        return generate(policySpec);
+    }
+
+    private PasswordPolicySpec merge(final List<PasswordPolicySpec> ppSpecs) {
+        PasswordPolicySpec fpps = new PasswordPolicySpec();
+        fpps.setMinLength(0);
+        fpps.setMaxLength(1000);
+
+        for (PasswordPolicySpec policySpec : ppSpecs) {
+            if (policySpec.getMinLength() > fpps.getMinLength()) {
+                fpps.setMinLength(policySpec.getMinLength());
+            }
+
+            if ((policySpec.getMaxLength() != 0) && ((policySpec.getMaxLength() < fpps.getMaxLength()))) {
+                fpps.setMaxLength(policySpec.getMaxLength());
+            }
+            fpps.getPrefixesNotPermitted().addAll(policySpec.getPrefixesNotPermitted());
+            fpps.getSuffixesNotPermitted().addAll(policySpec.getSuffixesNotPermitted());
+
+            if (!fpps.isNonAlphanumericRequired()) {
+                fpps.setNonAlphanumericRequired(policySpec.isNonAlphanumericRequired());
+            }
+
+            if (!fpps.isAlphanumericRequired()) {
+                fpps.setAlphanumericRequired(policySpec.isAlphanumericRequired());
+            }
+            if (!fpps.isDigitRequired()) {
+                fpps.setDigitRequired(policySpec.isDigitRequired());
+            }
+
+            if (!fpps.isLowercaseRequired()) {
+                fpps.setLowercaseRequired(policySpec.isLowercaseRequired());
+            }
+            if (!fpps.isUppercaseRequired()) {
+                fpps.setUppercaseRequired(policySpec.isUppercaseRequired());
+            }
+            if (!fpps.isMustStartWithDigit()) {
+                fpps.setMustStartWithDigit(policySpec.isMustStartWithDigit());
+            }
+            if (!fpps.isMustntStartWithDigit()) {
+                fpps.setMustntStartWithDigit(policySpec.isMustntStartWithDigit());
+            }
+            if (!fpps.isMustEndWithDigit()) {
+                fpps.setMustEndWithDigit(policySpec.isMustEndWithDigit());
+            }
+            if (fpps.isMustntEndWithDigit()) {
+                fpps.setMustntEndWithDigit(policySpec.isMustntEndWithDigit());
+            }
+            if (!fpps.isMustStartWithAlpha()) {
+                fpps.setMustStartWithAlpha(policySpec.isMustStartWithAlpha());
+            }
+            if (!fpps.isMustntStartWithAlpha()) {
+                fpps.setMustntStartWithAlpha(policySpec.isMustntStartWithAlpha());
+            }
+            if (!fpps.isMustStartWithNonAlpha()) {
+                fpps.setMustStartWithNonAlpha(policySpec.isMustStartWithNonAlpha());
+            }
+            if (!fpps.isMustntStartWithNonAlpha()) {
+                fpps.setMustntStartWithNonAlpha(policySpec.isMustntStartWithNonAlpha());
+            }
+            if (!fpps.isMustEndWithNonAlpha()) {
+                fpps.setMustEndWithNonAlpha(policySpec.isMustEndWithNonAlpha());
+            }
+            if (!fpps.isMustntEndWithNonAlpha()) {
+                fpps.setMustntEndWithNonAlpha(policySpec.isMustntEndWithNonAlpha());
+            }
+            if (!fpps.isMustEndWithAlpha()) {
+                fpps.setMustEndWithAlpha(policySpec.isMustEndWithAlpha());
+            }
+            if (!fpps.isMustntEndWithAlpha()) {
+                fpps.setMustntEndWithAlpha(policySpec.isMustntEndWithAlpha());
+            }
+        }
+        return fpps;
+    }
+
+    private void check(final PasswordPolicySpec policySpec)
+            throws InvalidPasswordPolicySpecException {
+
+        if (policySpec.getMinLength() == 0) {
+            throw new InvalidPasswordPolicySpecException("Minimum length is zero");
+        }
+        if (policySpec.isMustEndWithAlpha() && policySpec.isMustntEndWithAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithAlpha and mustntEndWithAlpha are both true");
+        }
+        if (policySpec.isMustEndWithAlpha() && policySpec.isMustEndWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithAlpha and mustEndWithDigit are both true");
+        }
+        if (policySpec.isMustEndWithDigit() && policySpec.isMustntEndWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithDigit and mustntEndWithDigit are both true");
+        }
+        if (policySpec.isMustEndWithNonAlpha() && policySpec.isMustntEndWithNonAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithNonAlpha and mustntEndWithNonAlpha are both true");
+        }
+        if (policySpec.isMustStartWithAlpha() && policySpec.isMustntStartWithAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithAlpha and mustntStartWithAlpha are both true");
+        }
+        if (policySpec.isMustStartWithAlpha() && policySpec.isMustStartWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithAlpha and mustStartWithDigit are both true");
+        }
+        if (policySpec.isMustStartWithDigit() && policySpec.isMustntStartWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithDigit and mustntStartWithDigit are both true");
+        }
+        if (policySpec.isMustStartWithNonAlpha() && policySpec.isMustntStartWithNonAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithNonAlpha and mustntStartWithNonAlpha are both true");
+        }
+        if (policySpec.getMinLength() > policySpec.getMaxLength()) {
+            throw new InvalidPasswordPolicySpecException("Minimun length (" + policySpec.getMinLength() + ")"
+                    + "is greater than maximum length (" + policySpec.getMaxLength() + ")");
+        }
+    }
+
+    private String generate(final PasswordPolicySpec policySpec) {
+        String[] generatedPassword = new String[policySpec.getMinLength()];
+
+        for (int i = 0; i < generatedPassword.length; i++) {
+            generatedPassword[i] = "";
+        }
+
+        checkStartChar(generatedPassword, policySpec);
+
+        checkEndChar(generatedPassword, policySpec);
+
+        checkRequired(generatedPassword, policySpec);
+
+        //filled empty chars
+        for (int firstEmptyChar = firstEmptyChar(generatedPassword);
+                firstEmptyChar < generatedPassword.length - 1; firstEmptyChar++) {
+            generatedPassword[firstEmptyChar] = SecureRandomUtil.generateRandomLetter();
+        }
+
+        checkPrefixAndSuffix(generatedPassword, policySpec);
+
+        return StringUtils.join(generatedPassword);
+    }
+
+    private void checkStartChar(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isMustStartWithAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustStartWithNonAlpha() || policySpec.isMustStartWithDigit()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomNumber();
+        }
+        if (policySpec.isMustntStartWithAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomNumber();
+
+        }
+        if (policySpec.isMustntStartWithDigit()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+
+        }
+        if (policySpec.isMustntStartWithNonAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+
+        }
+    }
+
+    private void checkEndChar(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isMustEndWithAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustEndWithNonAlpha() || policySpec.isMustEndWithDigit()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomNumber();
+        }
+
+        if (policySpec.isMustntEndWithAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomNumber();
+        }
+        if (policySpec.isMustntEndWithDigit()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustntEndWithNonAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+
+        }
+    }
+
+    private int firstEmptyChar(final String[] generatedPStrings) {
+        int index = 0;
+        while (!generatedPStrings[index].isEmpty()) {
+            index++;
+        }
+        return index;
+    }
+
+    private void checkRequired(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isDigitRequired()
+                && !PolicyPattern.DIGIT.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomNumber();
+        }
+
+        if (policySpec.isUppercaseRequired()
+                && !PolicyPattern.ALPHA_UPPERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomLetter().toUpperCase();
+        }
+
+        if (policySpec.isLowercaseRequired()
+                && !PolicyPattern.ALPHA_LOWERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomLetter().toLowerCase();
+        }
+
+        if (policySpec.isNonAlphanumericRequired()
+                && !PolicyPattern.NON_ALPHANUMERIC.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] =
+                    SecureRandomUtil.generateRandomSpecialCharacter(SPECIAL_CHARS);
+        }
+    }
+
+    private void checkPrefixAndSuffix(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        for (String prefix : policySpec.getPrefixesNotPermitted()) {
+            if (StringUtils.join(generatedPassword).startsWith(prefix)) {
+                checkStartChar(generatedPassword, policySpec);
+            }
+        }
+
+        for (String suffix : policySpec.getSuffixesNotPermitted()) {
+            if (StringUtils.join(generatedPassword).endsWith(suffix)) {
+                checkEndChar(generatedPassword, policySpec);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/SecureRandomUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/SecureRandomUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/SecureRandomUtil.java
new file mode 100644
index 0000000..50d3259
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/SecureRandomUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.utils;
+
+import java.security.SecureRandom;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+public final class SecureRandomUtil {
+
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    public static String generateRandomPassword(final int tokenLength) {
+        return RandomStringUtils.random(tokenLength, 0, 0, true, false, null, RANDOM);
+    }
+
+    public static String generateRandomLetter() {
+        return RandomStringUtils.random(1, 0, 0, true, false, null, RANDOM);
+    }
+
+    public static String generateRandomNumber() {
+        return RandomStringUtils.random(1, 0, 0, false, true, null, RANDOM);
+    }
+
+    public static String generateRandomSpecialCharacter(char[] characters) {
+        return RandomStringUtils.random(1, 0, 0, false, false, characters, RANDOM);
+    }
+
+    private SecureRandomUtil() {
+        // private constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
deleted file mode 100644
index 903b8ca..0000000
--- a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.server.utils;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-public final class URIUtil {
-
-    private URIUtil() {
-        // empty constructor for static utility class
-    }
-
-    /**
-     * Build a valid URI out of the given location.
-     * Only "file", "connid" and "connids" schemes are allowed.
-     * For "file", invalid characters are handled via intermediate transformation into URL.
-     *
-     * @param location the candidate location for URI
-     * @return valid URI for the given location
-     * @throws MalformedURLException if the intermediate URL is not valid
-     * @throws URISyntaxException if the given location does not correspond to a valid URI
-     */
-    public static URI buildForConnId(final String location) throws MalformedURLException, URISyntaxException {
-        final String candidate = location.trim();
-
-        if (!candidate.startsWith("file:")
-                && !candidate.startsWith("connid:") && !candidate.startsWith("connids:")) {
-
-            throw new IllegalArgumentException(candidate + " is not a valid URI for file or connid(s) schemes");
-        }
-
-        URI uri;
-        if (candidate.startsWith("file:")) {
-            uri = new File(new URL(candidate).getFile()).getAbsoluteFile().toURI();
-        } else {
-            uri = new URI(candidate);
-        }
-
-        return uri;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/UnwrappedObjectMapper.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/UnwrappedObjectMapper.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/UnwrappedObjectMapper.java
new file mode 100644
index 0000000..29bce71
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/UnwrappedObjectMapper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.server.utils.serialization;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * Jackson ObjectMapper that unwraps singleton map values and enable default
+ * typing for handling abstract types serialization.
+ */
+public class UnwrappedObjectMapper extends ObjectMapper {
+
+    private static final long serialVersionUID = -317191546835195103L;
+
+    /**
+     * Unwraps the given value if it implements the Map interface and contains
+     * only a single entry. Otherwise the value is returned unmodified.
+     *
+     * @param value the potential Map to unwrap
+     * @return the unwrapped map or the original value
+     */
+    private Object unwrapMap(final Object value) {
+        if (value instanceof Map) {
+            Map<?, ?> map = (Map<?, ?>) value;
+            if (map.size() == 1) {
+                return map.values().iterator().next();
+            }
+        }
+
+        return value;
+    }
+
+    @Override
+    public void writeValue(final JsonGenerator jgen, final Object value)
+            throws IOException, JsonGenerationException, JsonMappingException {
+
+        super.writeValue(jgen, unwrapMap(value));
+    }
+
+    @Override
+    public void writeValue(final File resultFile, final Object value)
+            throws IOException, JsonGenerationException, JsonMappingException {
+
+        super.writeValue(resultFile, unwrapMap(value));
+    }
+
+    @Override
+    public void writeValue(final OutputStream out, final Object value)
+            throws IOException, JsonGenerationException, JsonMappingException {
+
+        super.writeValue(out, unwrapMap(value));
+    }
+
+    @Override
+    public void writeValue(final Writer w, final Object value)
+            throws IOException, JsonGenerationException, JsonMappingException {
+
+        super.writeValue(w, unwrapMap(value));
+    }
+
+    @Override
+    public byte[] writeValueAsBytes(final Object value) throws JsonProcessingException {
+        return super.writeValueAsBytes(unwrapMap(value));
+    }
+
+    @Override
+    public String writeValueAsString(final Object value) throws JsonProcessingException {
+        return super.writeValueAsString(unwrapMap(value));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/pom.xml b/syncope620/server/workflow-api/pom.xml
new file mode 100644
index 0000000..af7bed8
--- /dev/null
+++ b/syncope620/server/workflow-api/pom.xml
@@ -0,0 +1,44 @@
+<?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-server</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Server Workflow API</name>
+  <description>Apache Syncope Server Workflow API</description>
+  <groupId>org.apache.syncope.server</groupId>
+  <artifactId>syncope-workflow-api</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-provisioning-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/RoleWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/RoleWorkflowAdapter.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/RoleWorkflowAdapter.java
new file mode 100644
index 0000000..76e0798
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/RoleWorkflowAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.workflow.api;
+
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+
+/**
+ * Interface for calling underlying workflow implementations.
+ */
+public interface RoleWorkflowAdapter extends WorkflowAdapter {
+
+    /**
+     * Create a role.
+     *
+     * @param roleTO role to be created and wether to propagate it as active
+     * @return role just created
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> create(RoleTO roleTO) throws WorkflowException;
+
+    /**
+     * Execute a task on a role.
+     *
+     * @param roleTO role to be subject to task
+     * @param taskId to be executed
+     * @return role just updated
+     * @throws NotFoundException role not found exception
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> execute(RoleTO roleTO, String taskId)
+            throws NotFoundException, WorkflowException;
+
+    /**
+     * Update a role.
+     *
+     * @param roleMod modification set to be performed
+     * @return role just updated and propagations to be performed
+     * @throws NotFoundException role not found exception
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> update(RoleMod roleMod)
+            throws NotFoundException, WorkflowException;
+
+    /**
+     * Delete a role.
+     *
+     * @param roleKey role to be deleted
+     * @throws NotFoundException role not found exception
+     * @throws WorkflowException workflow exception
+     */
+    void delete(Long roleKey) throws NotFoundException, WorkflowException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/UserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/UserWorkflowAdapter.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/UserWorkflowAdapter.java
new file mode 100644
index 0000000..1c521f1
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/UserWorkflowAdapter.java
@@ -0,0 +1,151 @@
+/*
+ * 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.server.workflow.api;
+
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.persistence.api.entity.user.User;
+
+/**
+ * Interface for calling underlying workflow implementations.
+ */
+public interface UserWorkflowAdapter extends WorkflowAdapter {
+
+    /**
+     * Create an user.
+     *
+     * @param userTO user to be created and whether to propagate it as active
+     * @param storePassword whether password shall be stored into the internal storage
+     * @return user just created
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Map.Entry<Long, Boolean>> create(UserTO userTO, boolean storePassword) throws
+            WorkflowException;
+
+    /**
+     * Create an user, optionally disabling password policy check.
+     *
+     * @param userTO user to be created and whether to propagate it as active
+     * @param disablePwdPolicyCheck disable password policy check?
+     * @param storePassword whether password shall be stored into the internal storage
+     * @return user just created
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Map.Entry<Long, Boolean>> create(UserTO userTO, boolean disablePwdPolicyCheck,
+            boolean storePassword) throws WorkflowException;
+
+    /**
+     * Create an user, optionally disabling password policy check.
+     *
+     * @param userTO user to be created and whether to propagate it as active
+     * @param disablePwdPolicyCheck disable password policy check?
+     * @param enabled specify true/false to force active/supended status
+     * @param storePassword whether password shall be stored into the internal storage
+     * @return user just created
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Map.Entry<Long, Boolean>> create(UserTO userTO, boolean disablePwdPolicyCheck, final Boolean enabled,
+            boolean storePassword) throws WorkflowException;
+
+    /**
+     * Execute a task on an user.
+     *
+     * @param userTO user to be subject to task
+     * @param taskId to be executed
+     * @return user just updated
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> execute(UserTO userTO, String taskId) throws WorkflowException;
+
+    /**
+     * Activate an user.
+     *
+     * @param userKey user to be activated
+     * @param token to be verified for activation
+     * @return user just updated
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> activate(Long userKey, String token) throws WorkflowException;
+
+    /**
+     * Update an user.
+     *
+     * @param userMod modification set to be performed
+     * @return user just updated and propagations to be performed
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Map.Entry<UserMod, Boolean>> update(UserMod userMod)
+            throws WorkflowException;
+
+    /**
+     * Suspend an user.
+     *
+     * @param userKey user to be suspended
+     * @return user just suspended
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> suspend(Long userKey) throws WorkflowException;
+
+    /**
+     * Suspend an user.
+     *
+     * @param user to be suspended
+     * @return user just suspended
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> suspend(User user) throws WorkflowException;
+
+    /**
+     * Reactivate an user.
+     *
+     * @param userKey user to be reactivated
+     * @return user just reactivated
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<Long> reactivate(Long userKey) throws WorkflowException;
+
+    /**
+     * Request password reset for an user.
+     *
+     * @param userKey user requesting password reset
+     * @throws WorkflowException workflow exception
+     */
+    void requestPasswordReset(Long userKey) throws WorkflowException;
+
+    /**
+     * Confirm password reset for an user.
+     *
+     * @param userKey user confirming password reset
+     * @param token security token
+     * @param password new password value
+     * @throws WorkflowException workflow exception
+     */
+    void confirmPasswordReset(Long userKey, String token, String password)
+            throws WorkflowException;
+
+    /**
+     * Delete an user.
+     *
+     * @param userKey user to be deleted
+     * @throws WorkflowException workflow exception
+     */
+    void delete(Long userKey) throws WorkflowException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowAdapter.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowAdapter.java
new file mode 100644
index 0000000..0b50031
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.server.workflow.api;
+
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import java.io.OutputStream;
+import java.util.List;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.to.WorkflowFormTO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+
+public interface WorkflowAdapter {
+
+    /**
+     * Give the class to be instantiated and invoked by SpringContextInitializer for loading anything needed by this
+     * adapter.
+     *
+     * @return null if no init is needed or the WorkflowLoader class for handling initialization
+     * @see org.apache.syncope.core.init.SpringContextInitializer
+     */
+    Class<? extends WorkflowInstanceLoader> getLoaderClass();
+
+    /**
+     * Export workflow definition.
+     *
+     * @param format export format
+     * @param os export stream
+     * @throws WorkflowException workflow exception
+     */
+    void exportDefinition(WorkflowDefinitionFormat format, OutputStream os) throws WorkflowException;
+
+    /**
+     * Export workflow graphical representation (if available).
+     *
+     * @param os export stream
+     * @throws WorkflowException workflow exception
+     */
+    void exportDiagram(OutputStream os) throws WorkflowException;
+
+    /**
+     * Update workflow definition.
+     *
+     * @param format import format
+     * @param definition definition
+     * @throws WorkflowException workflow exception
+     */
+    void importDefinition(WorkflowDefinitionFormat format, String definition) throws WorkflowException;
+
+    /**
+     * Get all defined forms for current workflow process instances.
+     *
+     * @return list of defined forms
+     */
+    List<WorkflowFormTO> getForms();
+
+    /**
+     * Gets all forms with the given name for the given workflowId(include historical forms).
+     *
+     * @param workflowId workflow id.
+     * @param name form name.
+     * @return forms (if present), otherwise an empty list.
+     */
+    List<WorkflowFormTO> getForms(String workflowId, String name);
+
+    /**
+     * Get form for given workflowId (if present).
+     *
+     * @param workflowId workflow id
+     * @return form (if present), otherwise null
+     * @throws NotFoundException definition not found exception
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowFormTO getForm(String workflowId) throws NotFoundException, WorkflowException;
+
+    /**
+     * Claim a form for a given user.
+     *
+     * @param taskId Workflow task to which the form is associated
+     * @return updated form
+     * @throws NotFoundException not found exception
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowFormTO claimForm(String taskId) throws NotFoundException, WorkflowException;
+
+    /**
+     * Submit a form.
+     *
+     * @param form to be submitted
+     * @return user updated by this form submit
+     * @throws NotFoundException not found exception
+     * @throws WorkflowException workflow exception
+     */
+    WorkflowResult<? extends AbstractAttributableMod> submitForm(WorkflowFormTO form)
+            throws NotFoundException, WorkflowException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowDefinitionFormat.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowDefinitionFormat.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowDefinitionFormat.java
new file mode 100644
index 0000000..41ffe1d
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowDefinitionFormat.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.workflow.api;
+
+/**
+ * Format for import / export of workflow definition.
+ */
+public enum WorkflowDefinitionFormat {
+
+    XML,
+    JSON
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowException.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowException.java
new file mode 100644
index 0000000..2468893
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowException.java
@@ -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.
+ */
+package org.apache.syncope.server.workflow.api;
+
+/**
+ * Wrapper for all workflow related exceptions. Original exceptions will depend on UserWorkflowAdapter implementation.
+ *
+ * @see UserWorkflowAdapter
+ */
+public class WorkflowException extends RuntimeException {
+
+    /**
+     * Generated serialVersionUID.
+     */
+    private static final long serialVersionUID = -6261173250078013869L;
+
+    /**
+     * Return a new instance wrapping the original workflow exception.
+     *
+     * @param cause original workflow exception
+     */
+    public WorkflowException(final Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Return a new instance wrapping the original workflow exception, additionally providing a local message.
+     *
+     * @param message local message
+     * @param cause original workflow exception
+     */
+    public WorkflowException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowInstanceLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowInstanceLoader.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowInstanceLoader.java
new file mode 100644
index 0000000..6271ec6
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/WorkflowInstanceLoader.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.workflow.api;
+
+public interface WorkflowInstanceLoader {
+
+    void load();
+
+    String getTablePrefix();
+
+    void init();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/package-info.java
----------------------------------------------------------------------
diff --git a/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/package-info.java b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/package-info.java
new file mode 100644
index 0000000..bb79624
--- /dev/null
+++ b/syncope620/server/workflow-api/src/main/java/org/apache/syncope/server/workflow/api/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.server.workflow.api;


[12/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java
new file mode 100644
index 0000000..5a006f5
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java
@@ -0,0 +1,67 @@
+/*
+ * 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.common.lib.search;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.SubjectType;
+
+public class SearchableFields {
+
+    protected static final String[] ATTRIBUTES_NOTINCLUDED = {
+        "plainAttrs", "derAttrs", "virAttrs",
+        "serialVersionUID", "memberships", "entitlements", "resources", "password",
+        "propagationTOs", "propagationStatusMap"
+    };
+
+    public static final List<String> get(final SubjectType subjectType) {
+        return get(subjectType == SubjectType.USER
+                ? UserTO.class
+                : RoleTO.class);
+    }
+
+    public static final List<String> get(final Class<? extends AbstractAttributableTO> attributableRef) {
+        final List<String> fieldNames = new ArrayList<>();
+
+        // loop on class and all superclasses searching for field
+        Class<?> clazz = attributableRef;
+        while (clazz != null && clazz != Object.class) {
+            for (Field field : clazz.getDeclaredFields()) {
+                if (!ArrayUtils.contains(ATTRIBUTES_NOTINCLUDED, field.getName())) {
+                    fieldNames.add(field.getName());
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+
+        Collections.reverse(fieldNames);
+        return fieldNames;
+
+    }
+
+    private SearchableFields() {
+        // empty constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
new file mode 100644
index 0000000..b4c2f21
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
@@ -0,0 +1,50 @@
+/*
+ * 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.common.lib.search;
+
+public enum SpecialAttr {
+
+    NULL("$null"),
+    RESOURCES("$resources"),
+    ROLES("$roles"),
+    ENTITLEMENTS("$entitlements");
+
+    private final String literal;
+
+    SpecialAttr(final String literal) {
+        this.literal = literal;
+    }
+
+    public static SpecialAttr fromString(final String value) {
+        SpecialAttr result = null;
+        for (SpecialAttr specialAttr : values()) {
+            if (specialAttr.literal.equals(value)) {
+                result = specialAttr;
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return literal;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeFiqlSearchConditionBuilder.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeFiqlSearchConditionBuilder.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeFiqlSearchConditionBuilder.java
new file mode 100644
index 0000000..6c710a2
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeFiqlSearchConditionBuilder.java
@@ -0,0 +1,110 @@
+/*
+ * 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.common.lib.search;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.cxf.jaxrs.ext.search.SearchUtils;
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+import org.apache.cxf.jaxrs.ext.search.client.FiqlSearchConditionBuilder;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+
+public abstract class SyncopeFiqlSearchConditionBuilder extends FiqlSearchConditionBuilder {
+
+    public static final Map<String, String> CONTEXTUAL_PROPERTIES;
+
+    static {
+        CONTEXTUAL_PROPERTIES = new HashMap<String, String>();
+        CONTEXTUAL_PROPERTIES.put(SearchUtils.LAX_PROPERTY_MATCH, "true");
+    }
+
+    protected SyncopeFiqlSearchConditionBuilder() {
+        super();
+    }
+
+    protected SyncopeFiqlSearchConditionBuilder(final Map<String, String> properties) {
+        super(properties);
+    }
+
+    @Override
+    protected Builder newBuilderInstance() {
+        return new Builder(properties);
+    }
+
+    public SyncopeProperty is(final String property) {
+        return newBuilderInstance().is(property);
+    }
+
+    public CompleteCondition isNull(final String property) {
+        return newBuilderInstance().is(property).nullValue();
+    }
+
+    public CompleteCondition isNotNull(final String property) {
+        return newBuilderInstance().is(property).notNullValue();
+    }
+
+    public CompleteCondition hasResources(final String resource, final String... moreResources) {
+        return newBuilderInstance().is(SpecialAttr.RESOURCES.toString()).hasResources(resource, moreResources);
+    }
+
+    public CompleteCondition hasNotResources(final String resource, final String... moreResources) {
+        return newBuilderInstance().is(SpecialAttr.RESOURCES.toString()).hasNotResources(resource, moreResources);
+    }
+
+    protected static class Builder extends FiqlSearchConditionBuilder.Builder
+            implements SyncopeProperty, CompleteCondition {
+
+        protected Builder(final Map<String, String> properties) {
+            super(properties);
+        }
+
+        protected Builder(final Builder parent) {
+            super(parent);
+        }
+
+        @Override
+        public SyncopeProperty is(final String property) {
+            Builder b = new Builder(this);
+            b.result = property;
+            return b;
+        }
+
+        @Override
+        public CompleteCondition nullValue() {
+            return condition(FiqlParser.EQ, SpecialAttr.NULL);
+        }
+
+        @Override
+        public CompleteCondition notNullValue() {
+            return condition(FiqlParser.NEQ, SpecialAttr.NULL);
+        }
+
+        @Override
+        public CompleteCondition hasResources(final String resource, final String... moreResources) {
+            this.result = SpecialAttr.RESOURCES.toString();
+            return condition(FiqlParser.EQ, resource, (Object[]) moreResources);
+        }
+
+        @Override
+        public CompleteCondition hasNotResources(final String resource, final String... moreResources) {
+            this.result = SpecialAttr.RESOURCES.toString();
+            return condition(FiqlParser.NEQ, resource, (Object[]) moreResources);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeProperty.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeProperty.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeProperty.java
new file mode 100644
index 0000000..0f833f8
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/SyncopeProperty.java
@@ -0,0 +1,37 @@
+/*
+ * 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.common.lib.search;
+
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+import org.apache.cxf.jaxrs.ext.search.client.Property;
+
+/**
+ * Extension of fluent interface, for {@link SyncopeFiqlSearchConditionBuilder}.
+ */
+public abstract interface SyncopeProperty extends Property {
+
+    CompleteCondition nullValue();
+
+    CompleteCondition notNullValue();
+    
+    CompleteCondition hasResources(String resource, String... moreResources);
+
+    CompleteCondition hasNotResources(String resource, String... moreResources);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
new file mode 100644
index 0000000..ef16e3d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserFiqlSearchConditionBuilder.java
@@ -0,0 +1,95 @@
+/*
+ * 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.common.lib.search;
+
+import java.util.Map;
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+
+/**
+ * Extends <tt>SyncopeFiqlSearchConditionBuilder</tt> by providing some additional facilities for searching
+ * users in Syncope.
+ */
+public class UserFiqlSearchConditionBuilder extends SyncopeFiqlSearchConditionBuilder {
+
+    public UserFiqlSearchConditionBuilder() {
+        super();
+    }
+
+    public UserFiqlSearchConditionBuilder(final Map<String, String> properties) {
+        super(properties);
+    }
+
+    @Override
+    protected Builder newBuilderInstance() {
+        return new Builder(properties);
+    }
+
+    @Override
+    public UserProperty is(final String property) {
+        return newBuilderInstance().is(property);
+    }
+
+    public CompleteCondition hasRoles(final Long role, final Long... moreRoles) {
+        return newBuilderInstance().is(SpecialAttr.ROLES.toString()).hasRoles(role, moreRoles);
+    }
+
+    public CompleteCondition hasNotRoles(final Long role, final Long... moreRoles) {
+        return newBuilderInstance().is(SpecialAttr.ROLES.toString()).hasNotRoles(role, moreRoles);
+    }
+
+    public CompleteCondition hasResources(final String resource, final String... moreResources) {
+        return newBuilderInstance().is(SpecialAttr.RESOURCES.toString()).hasResources(resource, moreResources);
+    }
+
+    public CompleteCondition hasNotResources(final String resource, final String... moreResources) {
+        return newBuilderInstance().is(SpecialAttr.RESOURCES.toString()).hasNotResources(resource, moreResources);
+    }
+
+    protected static class Builder extends SyncopeFiqlSearchConditionBuilder.Builder
+            implements UserProperty, CompleteCondition {
+
+        public Builder(final Map<String, String> properties) {
+            super(properties);
+        }
+
+        public Builder(final Builder parent) {
+            super(parent);
+        }
+
+        @Override
+        public UserProperty is(final String property) {
+            Builder b = new Builder(this);
+            b.result = property;
+            return b;
+        }
+
+        @Override
+        public CompleteCondition hasRoles(final Long role, final Long... moreRoles) {
+            this.result = SpecialAttr.ROLES.toString();
+            return condition(FiqlParser.EQ, role, (Object[]) moreRoles);
+        }
+
+        @Override
+        public CompleteCondition hasNotRoles(final Long role, final Long... moreRoles) {
+            this.result = SpecialAttr.ROLES.toString();
+            return condition(FiqlParser.NEQ, role, (Object[]) moreRoles);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
new file mode 100644
index 0000000..886f27f
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/UserProperty.java
@@ -0,0 +1,29 @@
+/*
+ * 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.common.lib.search;
+
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+
+public interface UserProperty extends SyncopeProperty {
+
+    CompleteCondition hasRoles(Long role, Long... moreRoles);
+
+    CompleteCondition hasNotRoles(Long role, Long... moreRoles);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractAttributableTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractAttributableTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractAttributableTO.java
index 5cb48bb..0794b09 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractAttributableTO.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractAttributableTO.java
@@ -34,18 +34,18 @@ public abstract class AbstractAttributableTO extends ConnObjectTO {
 
     private static final long serialVersionUID = 4083884098736820255L;
 
-    private long id;
+    private long key;
 
     private final List<AttrTO> derAttrs = new ArrayList<>();
 
     private final List<AttrTO> virAttrs = new ArrayList<>();
 
-    public long getId() {
-        return id;
+    public long getKey() {
+        return key;
     }
 
-    public void setId(final long id) {
-        this.id = id;
+    public void setKey(final long id) {
+        this.key = id;
     }
 
     @JsonIgnore

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractExecTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractExecTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractExecTO.java
new file mode 100644
index 0000000..71b735f
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractExecTO.java
@@ -0,0 +1,87 @@
+/*
+ * 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.common.lib.to;
+
+import java.util.Date;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlType
+public class AbstractExecTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -4621191979198357081L;
+
+    protected long key;
+
+    protected String status;
+
+    protected String message;
+
+    protected Date startDate;
+
+    protected Date endDate;
+
+    public long getKey() {
+        return key;
+    }
+
+    public void setKey(long key) {
+        this.key = key;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(final String message) {
+        this.message = message;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(final String status) {
+        this.status = status;
+    }
+
+    public Date getStartDate() {
+        return startDate == null
+                ? null
+                : new Date(startDate.getTime());
+    }
+
+    public void setStartDate(final Date startDate) {
+        if (startDate != null) {
+            this.startDate = new Date(startDate.getTime());
+        }
+    }
+
+    public Date getEndDate() {
+        return endDate == null
+                ? null
+                : new Date(endDate.getTime());
+    }
+
+    public void setEndDate(final Date endDate) {
+        if (endDate != null) {
+            this.endDate = new Date(endDate.getTime());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractPolicyTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractPolicyTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractPolicyTO.java
new file mode 100644
index 0000000..4423e41
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractPolicyTO.java
@@ -0,0 +1,89 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.PolicyType;
+
+@XmlRootElement(name = "abstractPolicy")
+@XmlType
+@XmlSeeAlso({ AccountPolicyTO.class, PasswordPolicyTO.class, SyncPolicyTO.class })
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public abstract class AbstractPolicyTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -2903888572649721035L;
+
+    private long id;
+
+    private String description;
+
+    private PolicyType type;
+
+    private final List<String> usedByResources = new ArrayList<>();
+
+    private final List<Long> usedByRoles = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public PolicyType getType() {
+        return type;
+    }
+
+    public void setType(final PolicyType type) {
+        this.type = type;
+    }
+
+    @XmlElementWrapper(name = "usedByResources")
+    @XmlElement(name = "resource")
+    @JsonProperty("usedByResources")
+    public List<String> getUsedByResources() {
+        return usedByResources;
+    }
+
+    @XmlElementWrapper(name = "usedByRoles")
+    @XmlElement(name = "role")
+    @JsonProperty("usedByRoles")
+    public List<Long> getUsedByRoles() {
+        return usedByRoles;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractProvisioningTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractProvisioningTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractProvisioningTaskTO.java
new file mode 100644
index 0000000..fe92877
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractProvisioningTaskTO.java
@@ -0,0 +1,117 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+
+@XmlRootElement(name = "abstractProvisioningTask")
+@XmlType
+@XmlSeeAlso({ PushTaskTO.class, SyncTaskTO.class })
+public class AbstractProvisioningTaskTO extends SchedTaskTO {
+
+    private static final long serialVersionUID = -2143537546915809016L;
+
+    private String resource;
+
+    private boolean performCreate;
+
+    private boolean performUpdate;
+
+    private boolean performDelete;
+
+    private boolean syncStatus;
+
+    private UnmatchingRule unmatchingRule;
+
+    private MatchingRule matchingRule;
+
+    private List<String> actionsClassNames = new ArrayList<>();
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public boolean isPerformCreate() {
+        return performCreate;
+    }
+
+    public void setPerformCreate(boolean performCreate) {
+        this.performCreate = performCreate;
+    }
+
+    public boolean isPerformUpdate() {
+        return performUpdate;
+    }
+
+    public void setPerformUpdate(boolean performUpdate) {
+        this.performUpdate = performUpdate;
+    }
+
+    public boolean isPerformDelete() {
+        return performDelete;
+    }
+
+    public void setPerformDelete(boolean performDelete) {
+        this.performDelete = performDelete;
+    }
+
+    public boolean isSyncStatus() {
+        return syncStatus;
+    }
+
+    public void setSyncStatus(boolean syncStatus) {
+        this.syncStatus = syncStatus;
+    }
+
+    @XmlElementWrapper(name = "actionsClassNames")
+    @XmlElement(name = "actionsClassName")
+    @JsonProperty("actionsClassNames")
+    public List<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
+
+    public UnmatchingRule getUnmatchingRule() {
+        return unmatchingRule;
+    }
+
+    public void setUnmatchingRule(final UnmatchingRule unmatchigRule) {
+        this.unmatchingRule = unmatchigRule;
+    }
+
+    public MatchingRule getMatchingRule() {
+        return matchingRule;
+    }
+
+    public void setMatchingRule(final MatchingRule matchigRule) {
+        this.matchingRule = matchigRule;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractSchemaTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractSchemaTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractSchemaTO.java
new file mode 100644
index 0000000..b3a37a8
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractSchemaTO.java
@@ -0,0 +1,44 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "abstractSchema")
+@XmlType
+@XmlSeeAlso({ PlainSchemaTO.class, DerSchemaTO.class, VirSchemaTO.class })
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public abstract class AbstractSchemaTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 4088388951694301759L;
+
+    private String key;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(final String key) {
+        this.key = key;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractTaskTO.java
new file mode 100644
index 0000000..6ed1750
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AbstractTaskTO.java
@@ -0,0 +1,98 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "abstractTask")
+@XmlType
+@XmlSeeAlso({ PropagationTaskTO.class, SchedTaskTO.class, NotificationTaskTO.class })
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public abstract class AbstractTaskTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 386450127003321197L;
+
+    private long id;
+
+    private String latestExecStatus;
+
+    private List<TaskExecTO> executions = new ArrayList<>();
+
+    private Date startDate;
+
+    private Date endDate;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(final long id) {
+        this.id = id;
+    }
+
+    public String getLatestExecStatus() {
+        return latestExecStatus;
+    }
+
+    public void setLatestExecStatus(final String latestExecStatus) {
+        this.latestExecStatus = latestExecStatus;
+    }
+
+    @XmlElementWrapper(name = "executions")
+    @XmlElement(name = "execution")
+    @JsonProperty("executions")
+    public List<TaskExecTO> getExecutions() {
+        return executions;
+    }
+
+    public Date getStartDate() {
+        return startDate == null
+                ? null
+                : new Date(startDate.getTime());
+    }
+
+    public void setStartDate(final Date startDate) {
+        if (startDate != null) {
+            this.startDate = new Date(startDate.getTime());
+        }
+    }
+
+    public Date getEndDate() {
+        return endDate == null
+                ? null
+                : new Date(endDate.getTime());
+    }
+
+    public void setEndDate(final Date endDate) {
+        if (endDate != null) {
+            this.endDate = new Date(endDate.getTime());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccountPolicyTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccountPolicyTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccountPolicyTO.java
new file mode 100644
index 0000000..a58b24f
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccountPolicyTO.java
@@ -0,0 +1,68 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+
+@XmlRootElement(name = "accountPolicy")
+@XmlType
+public class AccountPolicyTO extends AbstractPolicyTO {
+
+    private static final long serialVersionUID = -1557150042828800134L;
+
+    private AccountPolicySpec specification;
+
+    private final List<String> resources = new ArrayList<String>();
+
+    public AccountPolicyTO() {
+        this(false);
+    }
+
+    public AccountPolicyTO(final boolean global) {
+        super();
+
+        PolicyType type = global
+                ? PolicyType.GLOBAL_ACCOUNT
+                : PolicyType.ACCOUNT;
+        setType(type);
+    }
+
+    public void setSpecification(final AccountPolicySpec specification) {
+        this.specification = specification;
+    }
+
+    public AccountPolicySpec getSpecification() {
+        return specification;
+    }
+
+    @XmlElementWrapper(name = "resources")
+    @XmlElement(name = "resource")
+    @JsonProperty("resources")
+    public List<String> getResources() {
+        return resources;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
new file mode 100644
index 0000000..a514dcb
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
@@ -0,0 +1,70 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "bulkAction")
+@XmlType
+public class BulkAction extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 1395353278878758961L;
+
+    @XmlEnum
+    @XmlType(name = "bulkActionType")
+    public enum Type {
+
+        DELETE,
+        REACTIVATE,
+        SUSPEND,
+        DRYRUN,
+        EXECUTE
+
+    }
+
+    private Type operation;
+
+    /**
+     * Serialized identifiers.
+     */
+    private final List<String> targets = new ArrayList<>();
+
+    public Type getOperation() {
+        return operation;
+    }
+
+    public void setOperation(final Type operation) {
+        this.operation = operation;
+    }
+
+    @XmlElementWrapper(name = "targets")
+    @XmlElement(name = "target")
+    @JsonProperty("targets")
+    public List<String> getTargets() {
+        return targets;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java
new file mode 100644
index 0000000..dfe52de
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java
@@ -0,0 +1,134 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "bulkActionResult")
+@XmlType
+public class BulkActionResult extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 2868894178821778133L;
+
+    @XmlEnum
+    @XmlType(name = "bulkActionStatus")
+    public enum Status {
+
+        // general bulk action result statuses
+        SUCCESS,
+        FAILURE,
+        // specific propagation task execution statuses
+        CREATED,
+        SUBMITTED,
+        UNSUBMITTED;
+
+    }
+
+    private final List<Result> results = new ArrayList<Result>();
+
+    @XmlElementWrapper(name = "result")
+    @XmlElement(name = "item")
+    @JsonProperty("result")
+    public List<Result> getResult() {
+        return results;
+    }
+
+    @JsonIgnore
+    public void add(final Object id, final Status status) {
+        if (id != null) {
+            results.add(new Result(id.toString(), status));
+        }
+    }
+
+    @JsonIgnore
+    public void add(final Object id, final String status) {
+        if (id != null) {
+            results.add(new Result(id.toString(), Status.valueOf(status.toUpperCase())));
+        }
+    }
+
+    @JsonIgnore
+    public Map<String, Status> getResultMap() {
+        final Map<String, Status> res = new HashMap<String, Status>();
+
+        for (Result result : results) {
+            res.put(result.getKey(), result.getValue());
+        }
+
+        return res;
+    }
+
+    @JsonIgnore
+    public List<String> getResultByStatus(final Status status) {
+        final List<String> res = new ArrayList<String>();
+
+        for (Result result : results) {
+            if (result.getValue() == status) {
+                res.add(result.getKey());
+            }
+        }
+
+        return res;
+    }
+
+    public static class Result extends AbstractBaseBean {
+
+        private static final long serialVersionUID = -1149681964161193232L;
+
+        private String key;
+
+        private Status value;
+
+        public Result() {
+            super();
+        }
+
+        public Result(final String key, final Status value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public Status getValue() {
+            return value;
+        }
+
+        public void setKey(final String key) {
+            this.key = key;
+        }
+
+        public void setValue(final Status value) {
+            this.value = value;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConfTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConfTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConfTO.java
new file mode 100644
index 0000000..4856a48
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConfTO.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "conf")
+@XmlType
+public class ConfTO extends AbstractAttributableTO {
+
+    private static final long serialVersionUID = -3825039700228595590L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnBundleTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnBundleTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnBundleTO.java
new file mode 100644
index 0000000..87583a4
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnBundleTO.java
@@ -0,0 +1,95 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.ConnConfPropSchema;
+
+@XmlRootElement(name = "connectorBundle")
+@XmlType
+public class ConnBundleTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 7215115961910138005L;
+
+    private String displayName;
+
+    private String location;
+
+    private String bundleName;
+
+    private String connectorName;
+
+    private String version;
+
+    private List<ConnConfPropSchema> properties = new ArrayList<>();
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(final String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(final String location) {
+        this.location = location;
+    }
+
+    public String getBundleName() {
+        return bundleName;
+    }
+
+    public void setBundleName(final String bundleName) {
+        this.bundleName = bundleName;
+    }
+
+    public String getConnectorName() {
+        return connectorName;
+    }
+
+    public void setConnectorName(final String connectorName) {
+        this.connectorName = connectorName;
+    }
+
+    @XmlElementWrapper(name = "properties")
+    @XmlElement(name = "connConfPropSchema")
+    @JsonProperty("properties")
+    public List<ConnConfPropSchema> getProperties() {
+        return properties;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(final String version) {
+        this.version = version;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnInstanceTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnInstanceTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnInstanceTO.java
new file mode 100644
index 0000000..5c25783
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnInstanceTO.java
@@ -0,0 +1,177 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.ConnectorCapability;
+
+@XmlRootElement(name = "connInstance")
+@XmlType
+public class ConnInstanceTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 2707778645445168671L;
+
+    private long key;
+
+    private String location;
+
+    private String connectorName;
+
+    private String bundleName;
+
+    private String version;
+
+    private final Set<ConnConfProperty> configuration;
+
+    private final Set<ConnectorCapability> capabilities;
+
+    private String displayName;
+
+    private Integer connRequestTimeout;
+
+    private ConnPoolConfTO poolConf;
+
+    public ConnInstanceTO() {
+        super();
+
+        configuration = new HashSet<>();
+        capabilities = EnumSet.noneOf(ConnectorCapability.class);
+    }
+
+    public long getKey() {
+        return key;
+    }
+
+    public void setKey(final long key) {
+        this.key = key;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(final String location) {
+        this.location = location;
+    }
+
+    public String getConnectorName() {
+        return connectorName;
+    }
+
+    public void setConnectorName(final String connectorname) {
+        this.connectorName = connectorname;
+    }
+
+    public String getBundleName() {
+        return bundleName;
+    }
+
+    public void setBundleName(final String bundlename) {
+        this.bundleName = bundlename;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(final String version) {
+        this.version = version;
+    }
+
+    @XmlElementWrapper(name = "configuration")
+    @XmlElement(name = "property")
+    @JsonProperty("configuration")
+    public Set<ConnConfProperty> getConfiguration() {
+        return this.configuration;
+    }
+
+    @JsonIgnore
+    public Map<String, ConnConfProperty> getConfigurationMap() {
+        Map<String, ConnConfProperty> result;
+
+        if (getConfiguration() == null) {
+            result = Collections.<String, ConnConfProperty>emptyMap();
+        } else {
+            result = new HashMap<>();
+            for (ConnConfProperty prop : getConfiguration()) {
+                result.put(prop.getSchema().getName(), prop);
+            }
+            result = Collections.unmodifiableMap(result);
+        }
+
+        return result;
+    }
+
+    @XmlElementWrapper(name = "capabilities")
+    @XmlElement(name = "capability")
+    @JsonProperty("capabilities")
+    public Set<ConnectorCapability> getCapabilities() {
+        return capabilities;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(final String displayName) {
+        this.displayName = displayName;
+    }
+
+    /**
+     * Get connector request timeout.
+     * It is not applied in case of sync, full reconciliation and search.
+     *
+     * @return timeout.
+     */
+    public Integer getConnRequestTimeout() {
+        return connRequestTimeout;
+    }
+
+    /**
+     * Set connector request timeout.
+     * It is not applied in case of sync, full reconciliation and search.
+     *
+     * @param connRequestTimeout timeout
+     */
+    public void setConnRequestTimeout(final Integer connRequestTimeout) {
+        this.connRequestTimeout = connRequestTimeout;
+    }
+
+    public ConnPoolConfTO getPoolConf() {
+        return poolConf;
+    }
+
+    public void setPoolConf(final ConnPoolConfTO poolConf) {
+        this.poolConf = poolConf;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/DerSchemaTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/DerSchemaTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/DerSchemaTO.java
new file mode 100644
index 0000000..dc84864
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/DerSchemaTO.java
@@ -0,0 +1,39 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "derivedSchema")
+@XmlType
+public class DerSchemaTO extends AbstractSchemaTO {
+
+    private static final long serialVersionUID = -6747399803792103108L;
+
+    private String expression;
+
+    public String getExpression() {
+        return expression;
+    }
+
+    public void setExpression(final String expression) {
+        this.expression = expression;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ErrorTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ErrorTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ErrorTO.java
new file mode 100644
index 0000000..e0f14ad
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ErrorTO.java
@@ -0,0 +1,66 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+
+@XmlRootElement(name = "error")
+@XmlType
+public class ErrorTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 2435764161719225927L;
+
+    private int status;
+
+    private ClientExceptionType type;
+
+    private final List<Object> elements = new ArrayList<>();
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(final int status) {
+        this.status = status;
+    }
+
+    public ClientExceptionType getType() {
+        return type;
+    }
+
+    public void setType(final ClientExceptionType type) {
+        this.type = type;
+    }
+
+    @XmlElementWrapper(name = "elements")
+    @XmlElement(name = "element")
+    @JsonProperty("elements")
+    public List<Object> getElements() {
+        return elements;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
new file mode 100644
index 0000000..8e11686
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
@@ -0,0 +1,89 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.AuditElements;
+
+@XmlRootElement(name = "eventCategory")
+@XmlType
+public class EventCategoryTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -4340060002701633401L;
+
+    private AuditElements.EventCategoryType type;
+
+    private String category;
+
+    private String subcategory;
+
+    private final List<String> events = new ArrayList<String>();
+
+    /**
+     * Constructor for Type.REST event category.
+     */
+    public EventCategoryTO() {
+        this.type = AuditElements.EventCategoryType.REST;
+    }
+
+    /**
+     * Constructor for the given Type event category.
+     */
+    public EventCategoryTO(final AuditElements.EventCategoryType type) {
+        this.type = type;
+    }
+
+    public AuditElements.EventCategoryType getType() {
+        return type;
+    }
+
+    public void setType(final AuditElements.EventCategoryType type) {
+        this.type = type == null ? AuditElements.EventCategoryType.CUSTOM : type;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public void setCategory(final String category) {
+        this.category = category;
+    }
+
+    public String getSubcategory() {
+        return subcategory;
+    }
+
+    public void setSubcategory(final String subcategory) {
+        this.subcategory = subcategory;
+    }
+
+    @XmlElementWrapper(name = "events")
+    @XmlElement(name = "event")
+    @JsonProperty("events")
+    public List<String> getEvents() {
+        return events;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
new file mode 100644
index 0000000..263ab97
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
@@ -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.
+ */
+package org.apache.syncope.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+
+@XmlRootElement(name = "logger")
+@XmlType
+public class LoggerTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -7794833835668648505L;
+
+    private String name;
+
+    private LoggerLevel level;
+
+    public LoggerLevel getLevel() {
+        return level;
+    }
+
+    public void setLevel(final LoggerLevel level) {
+        this.level = level;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingItemTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingItemTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingItemTO.java
new file mode 100644
index 0000000..4df561c
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingItemTO.java
@@ -0,0 +1,134 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+
+@XmlRootElement(name = "mappingItem")
+@XmlType
+public class MappingItemTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 2983498836767176862L;
+
+    private Long key;
+
+    /**
+     * Attribute schema to be mapped. Consider that we can associate tha same attribute schema more than once, with
+     * different aliases, to different resource attributes.
+     */
+    private String intAttrName;
+
+    /**
+     * Schema type to be mapped.
+     */
+    private IntMappingType intMappingType;
+
+    /**
+     * External resource's field to be mapped.
+     */
+    private String extAttrName;
+
+    /**
+     * Specify if the mapped target resource's field is the key.
+     */
+    private boolean accountid;
+
+    /**
+     * Specify if the mapped target resource's field is the password.
+     */
+    private boolean password;
+
+    /**
+     * Specify if the mapped target resource's field is nullable.
+     */
+    private String mandatoryCondition = "false";
+
+    /**
+     * Mapping purposes: SYNCHRONIZATION, PROPAGATION, BOTH, NONE.
+     */
+    private MappingPurpose purpose;
+
+    public boolean isAccountid() {
+        return accountid;
+    }
+
+    public void setAccountid(final boolean accountid) {
+        this.accountid = accountid;
+    }
+
+    public String getExtAttrName() {
+        return extAttrName;
+    }
+
+    public void setExtAttrName(final String extAttrName) {
+        this.extAttrName = extAttrName;
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+    public void setKey(final Long key) {
+        this.key = key;
+    }
+
+    public String getMandatoryCondition() {
+        return mandatoryCondition;
+    }
+
+    public void setMandatoryCondition(final String mandatoryCondition) {
+        this.mandatoryCondition = mandatoryCondition;
+    }
+
+    public boolean isPassword() {
+        return password;
+    }
+
+    public void setPassword(final boolean password) {
+        this.password = password;
+    }
+
+    public String getIntAttrName() {
+        return intAttrName;
+    }
+
+    public void setIntAttrName(final String intAttrName) {
+        this.intAttrName = intAttrName;
+    }
+
+    public IntMappingType getIntMappingType() {
+        return intMappingType;
+    }
+
+    public void setIntMappingType(final IntMappingType intMappingType) {
+        this.intMappingType = intMappingType;
+    }
+
+    public MappingPurpose getPurpose() {
+        return purpose;
+    }
+
+    public void setPurpose(final MappingPurpose purpose) {
+        this.purpose = purpose;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingTO.java
new file mode 100644
index 0000000..3d29914
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/MappingTO.java
@@ -0,0 +1,120 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.IntMappingType;
+
+@XmlRootElement(name = "mapping")
+@XmlType
+public class MappingTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 8447688036282611118L;
+
+    private String accountLink;
+
+    private final List<MappingItemTO> items = new ArrayList<>();
+
+    public String getAccountLink() {
+        return accountLink;
+    }
+
+    public void setAccountLink(final String accountLink) {
+        this.accountLink = accountLink;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends MappingItemTO> T getAccountIdItem() {
+        T accountIdItem = null;
+        for (MappingItemTO item : getItems()) {
+            if (item.isAccountid()) {
+                accountIdItem = (T) item;
+            }
+        }
+        return accountIdItem;
+    }
+
+    protected <T extends MappingItemTO> boolean addAccountIdItem(final T accountIdItem) {
+        if (IntMappingType.UserVirtualSchema == accountIdItem.getIntMappingType()
+                || IntMappingType.RoleVirtualSchema == accountIdItem.getIntMappingType()
+                || IntMappingType.MembershipVirtualSchema == accountIdItem.getIntMappingType()
+                || IntMappingType.Password == accountIdItem.getIntMappingType()) {
+
+            throw new IllegalArgumentException("Virtual attributes cannot be set as accountId");
+        }
+        if (IntMappingType.Password == accountIdItem.getIntMappingType()) {
+            throw new IllegalArgumentException("Password attributes cannot be set as accountId");
+        }
+
+        accountIdItem.setExtAttrName(accountIdItem.getExtAttrName());
+        accountIdItem.setAccountid(true);
+
+        return this.addItem(accountIdItem);
+    }
+
+    public boolean setAccountIdItem(final MappingItemTO accountIdItem) {
+        if (accountIdItem == null) {
+            return this.removeItem(getAccountIdItem());
+        } else {
+            return addAccountIdItem(accountIdItem);
+        }
+    }
+
+    public MappingItemTO getPasswordItem() {
+        MappingItemTO passwordItem = null;
+        for (MappingItemTO item : getItems()) {
+            if (item.isPassword()) {
+                passwordItem = item;
+            }
+        }
+        return passwordItem;
+    }
+
+    public boolean setPasswordItem(final MappingItemTO passwordItem) {
+        if (passwordItem == null) {
+            return this.removeItem(getPasswordItem());
+        } else {
+            passwordItem.setExtAttrName(null);
+            passwordItem.setPassword(true);
+            return addItem(passwordItem);
+        }
+    }
+
+    @XmlElementWrapper(name = "items")
+    @XmlElement(name = "item")
+    @JsonProperty("items")
+    public List<MappingItemTO> getItems() {
+        return items;
+    }
+
+    public boolean addItem(final MappingItemTO item) {
+        return item == null ? false : this.items.contains(item) || this.items.add(item);
+    }
+
+    public boolean removeItem(final MappingItemTO item) {
+        return this.items.remove(item);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTO.java
new file mode 100644
index 0000000..b921e53
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTO.java
@@ -0,0 +1,175 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+
+@XmlRootElement(name = "notification")
+@XmlType
+public class NotificationTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -6145117115632592612L;
+
+    private Long key;
+
+    private List<String> events = new ArrayList<>();
+
+    private String userAbout;
+
+    private String roleAbout;
+
+    private String recipients;
+
+    private List<String> staticRecipients = new ArrayList<>();
+
+    private IntMappingType recipientAttrType;
+
+    private String recipientAttrName;
+
+    private boolean selfAsRecipient;
+
+    private String sender;
+
+    private String subject;
+
+    private String template;
+
+    private TraceLevel traceLevel;
+
+    private boolean active;
+
+    public String getUserAbout() {
+        return userAbout;
+    }
+
+    public void setUserAbout(final String userAbout) {
+        this.userAbout = userAbout;
+    }
+
+    public String getRoleAbout() {
+        return roleAbout;
+    }
+
+    public void setRoleAbout(final String roleAbout) {
+        this.roleAbout = roleAbout;
+    }
+
+    @XmlElementWrapper(name = "events")
+    @XmlElement(name = "event")
+    @JsonProperty("events")
+    public List<String> getEvents() {
+        return events;
+    }
+
+    @XmlElementWrapper(name = "staticRecipients")
+    @XmlElement(name = "staticRecipient")
+    @JsonProperty("staticRecipients")
+    public List<String> getStaticRecipients() {
+        return staticRecipients;
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+    public void setKey(Long key) {
+        this.key = key;
+    }
+
+    public String getRecipients() {
+        return recipients;
+    }
+
+    public void setRecipients(final String recipients) {
+        this.recipients = recipients;
+    }
+
+    public String getRecipientAttrName() {
+        return recipientAttrName;
+    }
+
+    public void setRecipientAttrName(final String recipientAttrName) {
+        this.recipientAttrName = recipientAttrName;
+    }
+
+    public IntMappingType getRecipientAttrType() {
+        return recipientAttrType;
+    }
+
+    public void setRecipientAttrType(final IntMappingType recipientAttrType) {
+        this.recipientAttrType = recipientAttrType;
+    }
+
+    public boolean isSelfAsRecipient() {
+        return selfAsRecipient;
+    }
+
+    public void setSelfAsRecipient(final boolean selfAsRecipient) {
+        this.selfAsRecipient = selfAsRecipient;
+    }
+
+    public String getSender() {
+        return sender;
+    }
+
+    public void setSender(final String sender) {
+        this.sender = sender;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(final String subject) {
+        this.subject = subject;
+    }
+
+    public String getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(final String template) {
+        this.template = template;
+    }
+
+    public TraceLevel getTraceLevel() {
+        return traceLevel;
+    }
+
+    public void setTraceLevel(final TraceLevel traceLevel) {
+        this.traceLevel = traceLevel;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void setActive(final boolean active) {
+        this.active = active;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
new file mode 100644
index 0000000..48661f9
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/NotificationTaskTO.java
@@ -0,0 +1,105 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+
+@XmlRootElement(name = "notificationTask")
+@XmlType
+public class NotificationTaskTO extends AbstractTaskTO {
+
+    private static final long serialVersionUID = 371671242591093846L;
+
+    private final Set<String> recipients = new HashSet<>();
+
+    private String sender;
+
+    private String subject;
+
+    private String textBody;
+
+    private String htmlBody;
+
+    private boolean executed;
+
+    private TraceLevel traceLevel;
+
+    @XmlElementWrapper(name = "recipients")
+    @XmlElement(name = "recipient")
+    @JsonProperty("recipients")
+    public Set<String> getRecipients() {
+        return recipients;
+    }
+
+    public String getSender() {
+        return sender;
+    }
+
+    public void setSender(final String sender) {
+        this.sender = sender;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(final String subject) {
+        this.subject = subject;
+    }
+
+    public String getTextBody() {
+        return textBody;
+    }
+
+    public void setTextBody(final String textBody) {
+        this.textBody = textBody;
+    }
+
+    public String getHtmlBody() {
+        return htmlBody;
+    }
+
+    public void setHtmlBody(final String htmlBody) {
+        this.htmlBody = htmlBody;
+    }
+
+    public boolean isExecuted() {
+        return executed;
+    }
+
+    public void setExecuted(boolean executed) {
+        this.executed = executed;
+    }
+
+    public TraceLevel getTraceLevel() {
+        return traceLevel;
+    }
+
+    public void setTraceLevel(final TraceLevel traceLevel) {
+        this.traceLevel = traceLevel;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PagedResult.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PagedResult.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PagedResult.java
new file mode 100644
index 0000000..f383ece
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PagedResult.java
@@ -0,0 +1,98 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "pagedResult")
+@XmlType
+public class PagedResult<T extends AbstractBaseBean> extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 3472875885259250934L;
+
+    private URI prev;
+
+    private URI next;
+
+    private final List<T> result = new ArrayList<T>();
+
+    private int page;
+
+    private int size;
+
+    private int totalCount;
+
+    public URI getPrev() {
+        return prev;
+    }
+
+    public void setPrev(final URI prev) {
+        this.prev = prev;
+    }
+
+    public URI getNext() {
+        return next;
+    }
+
+    public void setNext(final URI next) {
+        this.next = next;
+    }
+
+    @XmlElementWrapper(name = "result")
+    @XmlElement(name = "item")
+    @JsonProperty("result")
+    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+    public List<T> getResult() {
+        return result;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(final int page) {
+        this.page = page;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(final int size) {
+        this.size = size;
+    }
+
+    public int getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(final int totalCount) {
+        this.totalCount = totalCount;
+    }
+
+}


[11/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordPolicyTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordPolicyTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordPolicyTO.java
new file mode 100644
index 0000000..05c4e5a
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordPolicyTO.java
@@ -0,0 +1,54 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+
+@XmlRootElement(name = "passwordPolicy")
+@XmlType
+public class PasswordPolicyTO extends AbstractPolicyTO {
+
+    private static final long serialVersionUID = -5606086441294799690L;
+
+    private PasswordPolicySpec specification;
+
+    public PasswordPolicyTO() {
+        this(false);
+    }
+
+    public PasswordPolicyTO(boolean global) {
+        super();
+
+        PolicyType type = global
+                ? PolicyType.GLOBAL_PASSWORD
+                : PolicyType.PASSWORD;
+        setType(type);
+    }
+
+    public void setSpecification(final PasswordPolicySpec specification) {
+        this.specification = specification;
+    }
+
+    public PasswordPolicySpec getSpecification() {
+        return specification;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PlainSchemaTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PlainSchemaTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PlainSchemaTO.java
new file mode 100644
index 0000000..4568e36
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PlainSchemaTO.java
@@ -0,0 +1,156 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+
+@XmlRootElement(name = "schema")
+@XmlType
+public class PlainSchemaTO extends AbstractSchemaTO {
+
+    private static final long serialVersionUID = -8133983392476990308L;
+
+    private AttrSchemaType type = AttrSchemaType.String;
+
+    private String mandatoryCondition;
+
+    private boolean multivalue;
+
+    private boolean uniqueConstraint;
+
+    private boolean readonly;
+
+    private String conversionPattern;
+
+    private String validatorClass;
+
+    private String enumerationValues;
+
+    private String enumerationKeys;
+
+    private String secretKey;
+
+    private CipherAlgorithm cipherAlgorithm;
+
+    private String mimeType;
+
+    public String getConversionPattern() {
+        return conversionPattern;
+    }
+
+    public void setConversionPattern(final String conversionPattern) {
+        this.conversionPattern = conversionPattern;
+    }
+
+    public String getMandatoryCondition() {
+        return StringUtils.isNotBlank(mandatoryCondition)
+                ? mandatoryCondition
+                : "false";
+    }
+
+    public void setMandatoryCondition(final String mandatoryCondition) {
+        this.mandatoryCondition = mandatoryCondition;
+    }
+
+    public boolean isMultivalue() {
+        return multivalue;
+    }
+
+    public void setMultivalue(final boolean multivalue) {
+        this.multivalue = multivalue;
+    }
+
+    public boolean isUniqueConstraint() {
+        return uniqueConstraint;
+    }
+
+    public void setUniqueConstraint(final boolean uniqueConstraint) {
+        this.uniqueConstraint = uniqueConstraint;
+    }
+
+    public boolean isReadonly() {
+        return readonly;
+    }
+
+    public void setReadonly(final boolean readonly) {
+        this.readonly = readonly;
+    }
+
+    public AttrSchemaType getType() {
+        return type;
+    }
+
+    public void setType(final AttrSchemaType type) {
+        this.type = type;
+    }
+
+    public String getValidatorClass() {
+        return validatorClass;
+    }
+
+    public void setValidatorClass(final String validatorClass) {
+        this.validatorClass = validatorClass;
+    }
+
+    public String getEnumerationValues() {
+        return enumerationValues;
+    }
+
+    public void setEnumerationValues(final String enumerationValues) {
+        this.enumerationValues = enumerationValues;
+    }
+
+    public String getEnumerationKeys() {
+        return enumerationKeys;
+    }
+
+    public void setEnumerationKeys(final String enumerationKeys) {
+        this.enumerationKeys = enumerationKeys;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(final String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    public CipherAlgorithm getCipherAlgorithm() {
+        return cipherAlgorithm;
+    }
+
+    public void setCipherAlgorithm(final CipherAlgorithm cipherAlgorithm) {
+        this.cipherAlgorithm = cipherAlgorithm;
+    }
+
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    public void setMimeType(final String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PropagationTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PropagationTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PropagationTaskTO.java
new file mode 100644
index 0000000..7c6ec08
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PropagationTaskTO.java
@@ -0,0 +1,123 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.SubjectType;
+
+@XmlRootElement(name = "propagationTask")
+@XmlType
+public class PropagationTaskTO extends AbstractTaskTO {
+
+    private static final long serialVersionUID = 386450127003321197L;
+
+    private PropagationMode propagationMode;
+
+    private ResourceOperation propagationOperation;
+
+    private String accountId;
+
+    private String oldAccountId;
+
+    private String xmlAttributes;
+
+    private String resource;
+
+    private String objectClassName;
+
+    private SubjectType subjectType;
+
+    private Long subjectId;
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(final String accountId) {
+        this.accountId = accountId;
+    }
+
+    public String getOldAccountId() {
+        return oldAccountId;
+    }
+
+    public void setOldAccountId(final String oldAccountId) {
+        this.oldAccountId = oldAccountId;
+    }
+
+    public PropagationMode getPropagationMode() {
+        return propagationMode;
+    }
+
+    public void setPropagationMode(final PropagationMode propagationMode) {
+        this.propagationMode = propagationMode;
+    }
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(final String resource) {
+        this.resource = resource;
+    }
+
+    public ResourceOperation getPropagationOperation() {
+        return propagationOperation;
+    }
+
+    public void setPropagationOperation(final ResourceOperation propagationOperation) {
+
+        this.propagationOperation = propagationOperation;
+    }
+
+    public String getXmlAttributes() {
+        return xmlAttributes;
+    }
+
+    public void setXmlAttributes(final String xmlAttributes) {
+        this.xmlAttributes = xmlAttributes;
+    }
+
+    public String getObjectClassName() {
+        return objectClassName;
+    }
+
+    public void setObjectClassName(final String objectClassName) {
+        this.objectClassName = objectClassName;
+    }
+
+    public SubjectType getSubjectType() {
+        return subjectType;
+    }
+
+    public void setSubjectType(final SubjectType subjectType) {
+        this.subjectType = subjectType;
+    }
+
+    public Long getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(final Long subjectId) {
+        this.subjectId = subjectId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PushTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PushTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PushTaskTO.java
new file mode 100644
index 0000000..13d2ff4
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/PushTaskTO.java
@@ -0,0 +1,49 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "pushTask")
+@XmlType
+public class PushTaskTO extends AbstractProvisioningTaskTO {
+
+    private static final long serialVersionUID = -2143537546915809018L;
+
+    private String userFilter;
+
+    private String roleFilter;
+
+    public String getUserFilter() {
+        return userFilter;
+    }
+
+    public void setUserFilter(final String filter) {
+        this.userFilter = filter;
+    }
+
+    public String getRoleFilter() {
+        return roleFilter;
+    }
+
+    public void setRoleFilter(final String roleFilter) {
+        this.roleFilter = roleFilter;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportExecTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportExecTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportExecTO.java
new file mode 100644
index 0000000..ef92b3b
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportExecTO.java
@@ -0,0 +1,40 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "reportExec")
+@XmlType
+public class ReportExecTO extends AbstractExecTO {
+
+    private static final long serialVersionUID = -1025555939724089215L;
+
+    private long report;
+
+    public long getReport() {
+        return report;
+    }
+
+    public void setReport(long report) {
+        this.report = report;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportTO.java
new file mode 100644
index 0000000..a8ae1ac
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReportTO.java
@@ -0,0 +1,151 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.report.AbstractReportletConf;
+
+@XmlRootElement(name = "report")
+@XmlType
+public class ReportTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 5274568072084814410L;
+
+    private long id;
+
+    private String name;
+
+    private List<AbstractReportletConf> reportletConfs = new ArrayList<>();
+
+    private String cronExpression;
+
+    private List<ReportExecTO> executions = new ArrayList<>();
+
+    private String latestExecStatus;
+
+    private Date lastExec;
+
+    private Date nextExec;
+
+    private Date startDate;
+
+    private Date endDate;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @XmlElementWrapper(name = "reportletConfs")
+    @XmlElement(name = "reportletConf")
+    @JsonProperty("reportletConfs")
+    public List<AbstractReportletConf> getReportletConfs() {
+        return reportletConfs;
+    }
+
+    public String getCronExpression() {
+        return cronExpression;
+    }
+
+    public void setCronExpression(final String cronExpression) {
+        this.cronExpression = cronExpression;
+    }
+
+    @XmlElementWrapper(name = "executions")
+    @XmlElement(name = "execution")
+    @JsonProperty("executions")
+    public List<ReportExecTO> getExecutions() {
+        return executions;
+    }
+
+    public String getLatestExecStatus() {
+        return latestExecStatus;
+    }
+
+    public void setLatestExecStatus(final String latestExecStatus) {
+        this.latestExecStatus = latestExecStatus;
+    }
+
+    public Date getLastExec() {
+        return lastExec == null
+                ? null
+                : new Date(lastExec.getTime());
+    }
+
+    public void setLastExec(final Date lastExec) {
+        if (lastExec != null) {
+            this.lastExec = new Date(lastExec.getTime());
+        }
+    }
+
+    public Date getNextExec() {
+        return nextExec == null
+                ? null
+                : new Date(nextExec.getTime());
+    }
+
+    public void setNextExec(final Date nextExec) {
+        if (nextExec != null) {
+            this.nextExec = new Date(nextExec.getTime());
+        }
+    }
+
+    public Date getStartDate() {
+        return startDate == null
+                ? null
+                : new Date(startDate.getTime());
+    }
+
+    public void setStartDate(final Date startDate) {
+        if (startDate != null) {
+            this.startDate = new Date(startDate.getTime());
+        }
+    }
+
+    public Date getEndDate() {
+        return endDate == null
+                ? null
+                : new Date(endDate.getTime());
+    }
+
+    public void setEndDate(final Date endDate) {
+        if (endDate != null) {
+            this.endDate = new Date(endDate.getTime());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
new file mode 100644
index 0000000..98f33d2
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -0,0 +1,270 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.common.lib.types.TraceLevel;
+
+@XmlRootElement(name = "resource")
+@XmlType
+public class ResourceTO extends AbstractAnnotatedBean {
+
+    private static final long serialVersionUID = -9193551354041698963L;
+
+    /**
+     * The resource identifier is the name.
+     */
+    private String name;
+
+    /**
+     * The resource type is identified by the associated connector.
+     */
+    private Long connectorId;
+
+    /**
+     * Convenience information: display name for the connector id.
+     */
+    private String connectorDisplayName;
+
+    private MappingTO umapping;
+
+    private MappingTO rmapping;
+
+    private boolean propagationPrimary;
+
+    private int propagationPriority;
+
+    private boolean randomPwdIfNotProvided;
+
+    private PropagationMode propagationMode;
+
+    private boolean enforceMandatoryCondition;
+
+    private TraceLevel createTraceLevel;
+
+    private TraceLevel updateTraceLevel;
+
+    private TraceLevel deleteTraceLevel;
+
+    private TraceLevel syncTraceLevel;
+
+    private Long passwordPolicy;
+
+    private Long accountPolicy;
+
+    private Long syncPolicy;
+
+    private Set<ConnConfProperty> connConfProperties;
+
+    private String usyncToken;
+
+    private String rsyncToken;
+
+    private List<String> propagationActionsClassNames = new ArrayList<>();
+
+    public ResourceTO() {
+        super();
+
+        connConfProperties = new HashSet<>();
+        propagationMode = PropagationMode.TWO_PHASES;
+        propagationPriority = 0;
+
+        createTraceLevel = TraceLevel.ALL;
+        updateTraceLevel = TraceLevel.ALL;
+        deleteTraceLevel = TraceLevel.ALL;
+        syncTraceLevel = TraceLevel.ALL;
+    }
+
+    public String getKey() {
+        return name;
+    }
+
+    public void setKey(final String key) {
+        this.name = key;
+    }
+
+    public boolean isEnforceMandatoryCondition() {
+        return enforceMandatoryCondition;
+    }
+
+    public void setEnforceMandatoryCondition(final boolean enforceMandatoryCondition) {
+        this.enforceMandatoryCondition = enforceMandatoryCondition;
+    }
+
+    public Long getConnectorId() {
+        return connectorId;
+    }
+
+    public void setConnectorId(final Long connectorId) {
+        this.connectorId = connectorId;
+    }
+
+    public String getConnectorDisplayName() {
+        return connectorDisplayName;
+    }
+
+    public void setConnectorDisplayName(final String connectorDisplayName) {
+        this.connectorDisplayName = connectorDisplayName;
+    }
+
+    public MappingTO getUmapping() {
+        return umapping;
+    }
+
+    public void setUmapping(final MappingTO umapping) {
+        this.umapping = umapping;
+    }
+
+    public MappingTO getRmapping() {
+        return rmapping;
+    }
+
+    public void setRmapping(final MappingTO rmapping) {
+        this.rmapping = rmapping;
+    }
+
+    public boolean isPropagationPrimary() {
+        return propagationPrimary;
+    }
+
+    public void setPropagationPrimary(final boolean propagationPrimary) {
+        this.propagationPrimary = propagationPrimary;
+    }
+
+    public int getPropagationPriority() {
+        return propagationPriority;
+    }
+
+    public void setPropagationPriority(final int propagationPriority) {
+        this.propagationPriority = propagationPriority;
+    }
+
+    public boolean isRandomPwdIfNotProvided() {
+        return randomPwdIfNotProvided;
+    }
+
+    public void setRandomPwdIfNotProvided(final boolean randomPwdIfNotProvided) {
+        this.randomPwdIfNotProvided = randomPwdIfNotProvided;
+    }
+
+    public PropagationMode getPropagationMode() {
+        return propagationMode;
+    }
+
+    public void setPropagationMode(final PropagationMode propagationMode) {
+        this.propagationMode = propagationMode;
+    }
+
+    public TraceLevel getCreateTraceLevel() {
+        return createTraceLevel;
+    }
+
+    public void setCreateTraceLevel(final TraceLevel createTraceLevel) {
+        this.createTraceLevel = createTraceLevel;
+    }
+
+    public TraceLevel getDeleteTraceLevel() {
+        return deleteTraceLevel;
+    }
+
+    public void setDeleteTraceLevel(final TraceLevel deleteTraceLevel) {
+        this.deleteTraceLevel = deleteTraceLevel;
+    }
+
+    public TraceLevel getUpdateTraceLevel() {
+        return updateTraceLevel;
+    }
+
+    public void setUpdateTraceLevel(final TraceLevel updateTraceLevel) {
+        this.updateTraceLevel = updateTraceLevel;
+    }
+
+    public Long getPasswordPolicy() {
+        return passwordPolicy;
+    }
+
+    public void setPasswordPolicy(final Long passwordPolicy) {
+        this.passwordPolicy = passwordPolicy;
+    }
+
+    public Long getAccountPolicy() {
+        return accountPolicy;
+    }
+
+    public void setAccountPolicy(final Long accountPolicy) {
+        this.accountPolicy = accountPolicy;
+    }
+
+    public Long getSyncPolicy() {
+        return syncPolicy;
+    }
+
+    public void setSyncPolicy(final Long syncPolicy) {
+        this.syncPolicy = syncPolicy;
+    }
+
+    @XmlElementWrapper(name = "connConfProperties")
+    @XmlElement(name = "property")
+    @JsonProperty("connConfProperties")
+    public Set<ConnConfProperty> getConnConfProperties() {
+        return connConfProperties;
+    }
+
+    public TraceLevel getSyncTraceLevel() {
+        return syncTraceLevel;
+    }
+
+    public void setSyncTraceLevel(final TraceLevel syncTraceLevel) {
+        this.syncTraceLevel = syncTraceLevel;
+    }
+
+    public String getUsyncToken() {
+        return usyncToken;
+    }
+
+    public void setUsyncToken(final String syncToken) {
+        this.usyncToken = syncToken;
+    }
+
+    public String getRsyncToken() {
+        return rsyncToken;
+    }
+
+    public void setRsyncToken(final String syncToken) {
+        this.rsyncToken = syncToken;
+    }
+
+    @XmlElementWrapper(name = "propagationActionsClassNames")
+    @XmlElement(name = "propagationActionsClassName")
+    @JsonProperty("propagationActionsClassNames")
+    public List<String> getPropagationActionsClassNames() {
+        return propagationActionsClassNames;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
index 79ddfb3..6e24929 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/RoleTO.java
@@ -239,7 +239,7 @@ public class RoleTO extends AbstractSubjectTO {
     }
 
     public String getDisplayName() {
-        return getId() + " " + getName();
+        return getKey() + " " + getName();
     }
 
     public static long fromDisplayName(final String displayName) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SchedTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SchedTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SchedTaskTO.java
new file mode 100644
index 0000000..330a12d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SchedTaskTO.java
@@ -0,0 +1,101 @@
+/*
+ * 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.common.lib.to;
+
+import java.util.Date;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "schedTask")
+@XmlType
+@XmlSeeAlso(AbstractProvisioningTaskTO.class)
+public class SchedTaskTO extends AbstractTaskTO {
+
+    private static final long serialVersionUID = -5722284116974636425L;
+
+    private String cronExpression;
+
+    private String jobClassName;
+
+    private String name;
+
+    private String description;
+
+    private Date lastExec;
+
+    private Date nextExec;
+
+    public String getCronExpression() {
+        return cronExpression;
+    }
+
+    public void setCronExpression(final String cronExpression) {
+        this.cronExpression = cronExpression;
+    }
+
+    public String getJobClassName() {
+        return jobClassName;
+    }
+
+    public void setJobClassName(final String jobClassName) {
+        this.jobClassName = jobClassName;
+    }
+
+    public Date getLastExec() {
+        return lastExec == null
+                ? null
+                : new Date(lastExec.getTime());
+    }
+
+    public void setLastExec(final Date lastExec) {
+        if (lastExec != null) {
+            this.lastExec = new Date(lastExec.getTime());
+        }
+    }
+
+    public Date getNextExec() {
+        return nextExec == null
+                ? null
+                : new Date(nextExec.getTime());
+    }
+
+    public void setNextExec(final Date nextExec) {
+        if (nextExec != null) {
+            this.nextExec = new Date(nextExec.getTime());
+        }
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SecurityQuestionTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SecurityQuestionTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SecurityQuestionTO.java
new file mode 100644
index 0000000..dd73f2c
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SecurityQuestionTO.java
@@ -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.
+ */
+package org.apache.syncope.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "securityQuestion")
+@XmlType
+public class SecurityQuestionTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 5969810939993556530L;
+
+    private long key;
+
+    private String content;
+
+    public long getKey() {
+        return key;
+    }
+
+    public void setKey(final long key) {
+        this.key = key;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(final String content) {
+        this.content = content;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncPolicyTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncPolicyTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncPolicyTO.java
new file mode 100644
index 0000000..4edcc43
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncPolicyTO.java
@@ -0,0 +1,54 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.common.lib.types.SyncPolicySpec;
+
+@XmlRootElement(name = "syncPolicy")
+@XmlType
+public class SyncPolicyTO extends AbstractPolicyTO {
+
+    private static final long serialVersionUID = 993024634238024242L;
+
+    private SyncPolicySpec specification;
+
+    public SyncPolicyTO() {
+        this(false);
+    }
+
+    public SyncPolicyTO(final boolean global) {
+        super();
+
+        PolicyType type = global
+                ? PolicyType.GLOBAL_SYNC
+                : PolicyType.SYNC;
+        setType(type);
+    }
+
+    public void setSpecification(final SyncPolicySpec specification) {
+        this.specification = specification;
+    }
+
+    public SyncPolicySpec getSpecification() {
+        return specification;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncTaskTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncTaskTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncTaskTO.java
new file mode 100644
index 0000000..9e33e96
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/SyncTaskTO.java
@@ -0,0 +1,59 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "syncTask")
+@XmlType
+public class SyncTaskTO extends AbstractProvisioningTaskTO {
+
+    private static final long serialVersionUID = -2143537546915809017L;
+
+    private UserTO userTemplate;
+
+    private RoleTO roleTemplate;
+
+    private boolean fullReconciliation;
+
+    public UserTO getUserTemplate() {
+        return userTemplate;
+    }
+
+    public void setUserTemplate(final UserTO userTemplate) {
+        this.userTemplate = userTemplate;
+    }
+
+    public RoleTO getRoleTemplate() {
+        return roleTemplate;
+    }
+
+    public void setRoleTemplate(final RoleTO roleTemplate) {
+        this.roleTemplate = roleTemplate;
+    }
+
+    public boolean isFullReconciliation() {
+        return fullReconciliation;
+    }
+
+    public void setFullReconciliation(boolean fullReconciliation) {
+        this.fullReconciliation = fullReconciliation;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/TaskExecTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/TaskExecTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/TaskExecTO.java
new file mode 100644
index 0000000..467c35e
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/TaskExecTO.java
@@ -0,0 +1,39 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "taskExec")
+@XmlType
+public class TaskExecTO extends AbstractExecTO {
+
+    private static final long serialVersionUID = -5401795154606268973L;
+
+    private long task;
+
+    public long getTask() {
+        return task;
+    }
+
+    public void setTask(final long task) {
+        this.task = task;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/VirSchemaTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/VirSchemaTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/VirSchemaTO.java
new file mode 100644
index 0000000..1a9d3bd
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/VirSchemaTO.java
@@ -0,0 +1,38 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "virtualSchema")
+public class VirSchemaTO extends AbstractSchemaTO {
+
+    private static final long serialVersionUID = -8198557479659701343L;
+
+    private boolean readonly;
+
+    public boolean isReadonly() {
+        return readonly;
+    }
+
+    public void setReadonly(final boolean readonly) {
+        this.readonly = readonly;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormPropertyTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormPropertyTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormPropertyTO.java
new file mode 100644
index 0000000..c89bfcd
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormPropertyTO.java
@@ -0,0 +1,119 @@
+/*
+ * 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.common.lib.to;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.WorkflowFormPropertyType;
+
+@XmlRootElement(name = "workflowFormProperty")
+@XmlType
+public class WorkflowFormPropertyTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 9139969592634304261L;
+
+    private String key;
+
+    private String name;
+
+    private WorkflowFormPropertyType type;
+
+    private String value;
+
+    private boolean readable;
+
+    private boolean writable;
+
+    private boolean required;
+
+    private String datePattern;
+
+    private Map<String, String> enumValues = new HashMap<>();
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public boolean isReadable() {
+        return readable;
+    }
+
+    public void setReadable(final boolean readable) {
+        this.readable = readable;
+    }
+
+    public boolean isRequired() {
+        return required;
+    }
+
+    public void setRequired(final boolean required) {
+        this.required = required;
+    }
+
+    public WorkflowFormPropertyType getType() {
+        return type;
+    }
+
+    public void setType(final WorkflowFormPropertyType type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(final String value) {
+        this.value = value;
+    }
+
+    public boolean isWritable() {
+        return writable;
+    }
+
+    public void setWritable(final boolean writable) {
+        this.writable = writable;
+    }
+
+    public String getDatePattern() {
+        return datePattern;
+    }
+
+    public void setDatePattern(final String datePattern) {
+        this.datePattern = datePattern;
+    }
+
+    public Map<String, String> getEnumValues() {
+        return enumValues;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormTO.java
new file mode 100644
index 0000000..f63d9fe
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/WorkflowFormTO.java
@@ -0,0 +1,162 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "workflowForm")
+@XmlType
+public class WorkflowFormTO extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -7044543391316529128L;
+
+    private long userKey;
+
+    private String taskId;
+
+    private String key;
+
+    private String description;
+
+    private Date createTime;
+
+    private Date dueDate;
+
+    private String owner;
+
+    private final List<WorkflowFormPropertyTO> properties;
+
+    public WorkflowFormTO() {
+        properties = new ArrayList<>();
+    }
+
+    public long getUserKey() {
+        return userKey;
+    }
+
+    public void setUserKey(long userKey) {
+        this.userKey = userKey;
+    }
+
+    public String getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(final String taskId) {
+        this.taskId = taskId;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(final Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public Date getDueDate() {
+        return dueDate;
+    }
+
+    public void setDueDate(final Date dueDate) {
+        this.dueDate = dueDate;
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public void setOwner(final String owner) {
+        this.owner = owner;
+    }
+
+    @XmlElementWrapper(name = "workflowFormProperties")
+    @XmlElement(name = "workflowFormProperty")
+    @JsonProperty("workflowFormProperties")
+    public List<WorkflowFormPropertyTO> getProperties() {
+        return properties;
+    }
+
+    public boolean addProperty(final WorkflowFormPropertyTO property) {
+        return properties.contains(property)
+                ? true
+                : properties.add(property);
+    }
+
+    public boolean removeProperty(final WorkflowFormPropertyTO property) {
+        return properties.remove(property);
+    }
+
+    @JsonIgnore
+    public Map<String, WorkflowFormPropertyTO> getPropertyMap() {
+        Map<String, WorkflowFormPropertyTO> result;
+
+        if (getProperties() == null) {
+            result = Collections.emptyMap();
+        } else {
+            result = new HashMap<>();
+            for (WorkflowFormPropertyTO prop : getProperties()) {
+                result.put(prop.getKey(), prop);
+            }
+            result = Collections.unmodifiableMap(result);
+        }
+
+        return result;
+    }
+
+    @JsonIgnore
+    public Map<String, String> getPropertiesForSubmit() {
+        Map<String, String> props = new HashMap<String, String>();
+        for (WorkflowFormPropertyTO prop : getProperties()) {
+            if (prop.isWritable()) {
+                props.put(prop.getKey(), prop.getValue());
+            }
+        }
+
+        return props;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java
new file mode 100644
index 0000000..b02ed8f
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NAMESPACE)
+package org.apache.syncope.common.lib.to;
+
+import javax.xml.bind.annotation.XmlSchema;
+import org.apache.syncope.common.lib.SyncopeConstants;

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
new file mode 100644
index 0000000..40ff86a
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
@@ -0,0 +1,61 @@
+/*
+ * 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.common.lib.types;
+
+import java.io.Serializable;
+import javax.xml.bind.annotation.XmlEnum;
+import org.apache.commons.lang3.StringUtils;
+
+public final class AuditElements implements Serializable {
+
+    private static final long serialVersionUID = -4385059255522273254L;
+
+    private AuditElements() {
+    }
+
+    @XmlEnum
+    public enum EventCategoryType {
+
+        REST(StringUtils.EMPTY),
+        TASK(StringUtils.EMPTY),
+        PROPAGATION("PropagationTask"),
+        SYNCHRONIZATION("SyncTask"),
+        PUSH("PushTask"),
+        CUSTOM(StringUtils.EMPTY);
+
+        private final String value;
+
+        EventCategoryType(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    @XmlEnum
+    public enum Result {
+
+        SUCCESS,
+        FAILURE
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
new file mode 100644
index 0000000..2e385fc
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
@@ -0,0 +1,222 @@
+/*
+ * 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.common.lib.types;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.text.ParseException;
+import java.util.AbstractMap;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.to.EventCategoryTO;
+import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+
+public class AuditLoggerName extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -647989486671786839L;
+
+    private final AuditElements.EventCategoryType type;
+
+    private final String category;
+
+    private final String subcategory;
+
+    private final String event;
+
+    private final Result result;
+
+    @JsonCreator
+    public AuditLoggerName(
+            @JsonProperty("type") final AuditElements.EventCategoryType type,
+            @JsonProperty("category") final String category,
+            @JsonProperty("subcategory") final String subcategory,
+            @JsonProperty("event") final String event,
+            @JsonProperty("result") final Result result)
+            throws IllegalArgumentException {
+
+        this.type = type == null ? AuditElements.EventCategoryType.CUSTOM : type;
+        this.category = category;
+        this.subcategory = subcategory;
+        this.event = event;
+        this.result = result == null ? Result.SUCCESS : result;
+    }
+
+    public AuditElements.EventCategoryType getType() {
+        return type;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public Result getResult() {
+        return result;
+    }
+
+    public String getSubcategory() {
+        return subcategory;
+    }
+
+    public String toLoggerName() {
+        return new StringBuilder().append(LoggerType.AUDIT.getPrefix()).append('.').
+                append(buildEvent(type, category, subcategory, event, result)).toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static AuditLoggerName fromLoggerName(final String loggerName)
+            throws IllegalArgumentException, ParseException {
+
+        if (StringUtils.isBlank(loggerName)) {
+            throw new IllegalArgumentException("Null value not permitted");
+        }
+
+        if (!loggerName.startsWith(LoggerType.AUDIT.getPrefix())) {
+            throw new ParseException("Audit logger name must start with " + LoggerType.AUDIT.getPrefix(), 0);
+        }
+
+        final Map.Entry<EventCategoryTO, Result> eventCategory = parseEventCategory(
+                loggerName.replaceAll(LoggerType.AUDIT.getPrefix() + ".", ""));
+
+        return new AuditLoggerName(
+                eventCategory.getKey().getType(),
+                eventCategory.getKey().getCategory(),
+                eventCategory.getKey().getSubcategory(),
+                eventCategory.getKey().getEvents().isEmpty()
+                        ? StringUtils.EMPTY : eventCategory.getKey().getEvents().iterator().next(),
+                eventCategory.getValue());
+    }
+
+    private static Map.Entry<EventCategoryTO, Result> parseEventCategory(final String event) {
+        final EventCategoryTO eventCategoryTO = new EventCategoryTO();
+
+        Result condition = null;
+
+        if (StringUtils.isNotEmpty(event)) {
+            final String[] elements = event.substring(1, event.length() - 1).split("\\]:\\[");
+
+            if (elements.length == 1) {
+                eventCategoryTO.setType(EventCategoryType.CUSTOM);
+                condition = Result.SUCCESS;
+                eventCategoryTO.getEvents().add(event);
+            } else {
+                EventCategoryType type;
+
+                if (EventCategoryType.PROPAGATION.toString().equals(elements[0])) {
+                    type = EventCategoryType.PROPAGATION;
+                } else if (EventCategoryType.SYNCHRONIZATION.toString().equals(elements[0])) {
+                    type = EventCategoryType.SYNCHRONIZATION;
+                } else if (EventCategoryType.PUSH.toString().equals(elements[0])) {
+                    type = EventCategoryType.PUSH;
+                } else {
+                    try {
+                        type = EventCategoryType.valueOf(elements[0]);
+                    } catch (Exception e) {
+                        type = EventCategoryType.CUSTOM;
+                    }
+                }
+
+                eventCategoryTO.setType(type);
+
+                eventCategoryTO.setCategory(StringUtils.isNotEmpty(elements[1]) ? elements[1] : null);
+
+                eventCategoryTO.setSubcategory(StringUtils.isNotEmpty(elements[2]) ? elements[2] : null);
+
+                if (elements.length > 3 && StringUtils.isNotEmpty(elements[3])) {
+                    eventCategoryTO.getEvents().add(elements[3]);
+                }
+
+                if (elements.length > 4) {
+                    condition = Result.valueOf(elements[4].toUpperCase());
+                }
+            }
+        }
+
+        return new AbstractMap.SimpleEntry< EventCategoryTO, Result>(eventCategoryTO, condition);
+    }
+
+    /**
+     * Build event string with the following syntax [type]:[category]:[subcategory]:[event]:[maybe result value cond].
+     *
+     * @param type event type.
+     * @param category event category.
+     * @param subcategory event subcategory.
+     * @param event event.
+     * @param resultValueCondition result value condition.
+     * @return event string.
+     */
+    public static String buildEvent(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final AuditElements.Result resultValueCondition) {
+
+        final StringBuilder eventBuilder = new StringBuilder();
+
+        eventBuilder.append('[');
+        if (type != null) {
+            if (StringUtils.isNotBlank(type.toString())) {
+                eventBuilder.append(type.toString());
+            } else {
+                eventBuilder.append(type.name());
+            }
+        }
+        eventBuilder.append(']');
+
+        eventBuilder.append(":");
+
+        eventBuilder.append('[');
+        if (StringUtils.isNotBlank(category)) {
+            eventBuilder.append(category);
+        }
+        eventBuilder.append(']');
+
+        eventBuilder.append(":");
+
+        eventBuilder.append('[');
+        if (StringUtils.isNotBlank(subcategory)) {
+            eventBuilder.append(subcategory);
+        }
+        eventBuilder.append(']');
+
+        eventBuilder.append(":");
+
+        eventBuilder.append('[');
+        if (StringUtils.isNotBlank(event)) {
+            eventBuilder.append(event);
+        }
+        eventBuilder.append(']');
+
+        if (resultValueCondition != null) {
+            eventBuilder.append(":");
+
+            eventBuilder.append('[');
+            eventBuilder.append(resultValueCondition);
+            eventBuilder.append(']');
+        }
+
+        return eventBuilder.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
new file mode 100644
index 0000000..0c272a9
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
@@ -0,0 +1,107 @@
+/*
+ * 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.common.lib.types;
+
+import javax.ws.rs.core.Response;
+
+public enum ClientExceptionType {
+
+    AssociatedResources(Response.Status.BAD_REQUEST),
+    Composite(Response.Status.BAD_REQUEST),
+    ConcurrentModification(Response.Status.PRECONDITION_FAILED),
+    DataIntegrityViolation(Response.Status.BAD_REQUEST),
+    EntityExists(Response.Status.CONFLICT),
+    GenericPersistence(Response.Status.BAD_REQUEST),
+    InvalidSecurityAnswer(Response.Status.BAD_REQUEST),
+    InvalidLogger(Response.Status.BAD_REQUEST),
+    InvalidConnInstance(Response.Status.BAD_REQUEST),
+    InvalidConnIdConf(Response.Status.BAD_REQUEST),
+    InvalidPolicy(Response.Status.BAD_REQUEST),
+    InvalidSyncopeConf(Response.Status.BAD_REQUEST),
+    InvalidSyncopeRole(Response.Status.BAD_REQUEST),
+    InvalidReportExec(Response.Status.BAD_REQUEST),
+    InvalidRoles(Response.Status.BAD_REQUEST),
+    InvalidSchemaDefinition(Response.Status.BAD_REQUEST),
+    InvalidSearchExpression(Response.Status.BAD_REQUEST),
+    InvalidPageOrSize(Response.Status.BAD_REQUEST),
+    InvalidPropagationTaskExecReport(Response.Status.BAD_REQUEST),
+    InvalidUSchema(Response.Status.BAD_REQUEST),
+    InvalidUDerSchema(Response.Status.BAD_REQUEST),
+    InvalidUVirSchema(Response.Status.BAD_REQUEST),
+    InvalidRSchema(Response.Status.BAD_REQUEST),
+    InvalidRDerSchema(Response.Status.BAD_REQUEST),
+    InvalidRVirSchema(Response.Status.BAD_REQUEST),
+    InvalidMSchema(Response.Status.BAD_REQUEST),
+    InvalidMDerSchema(Response.Status.BAD_REQUEST),
+    InvalidMVirSchema(Response.Status.BAD_REQUEST),
+    InvalidCSchema(Response.Status.BAD_REQUEST),
+    InvalidSchemaMapping(Response.Status.BAD_REQUEST),
+    InvalidSyncopeUser(Response.Status.BAD_REQUEST),
+    InvalidExternalResource(Response.Status.BAD_REQUEST),
+    InvalidNotification(Response.Status.BAD_REQUEST),
+    InvalidPropagationTask(Response.Status.BAD_REQUEST),
+    InvalidSchedTask(Response.Status.BAD_REQUEST),
+    InvalidSyncTask(Response.Status.BAD_REQUEST),
+    InvalidPushTask(Response.Status.BAD_REQUEST),
+    InvalidValues(Response.Status.BAD_REQUEST),
+    NotFound(Response.Status.NOT_FOUND),
+    RejectedUserCreate(Response.Status.BAD_REQUEST),
+    RequiredValuesMissing(Response.Status.BAD_REQUEST),
+    RESTValidation(Response.Status.BAD_REQUEST),
+    RoleOwnership(Response.Status.BAD_REQUEST),
+    Scheduling(Response.Status.BAD_REQUEST),
+    UnauthorizedRole(Response.Status.UNAUTHORIZED),
+    Unauthorized(Response.Status.UNAUTHORIZED),
+    Unknown(Response.Status.BAD_REQUEST),
+    Workflow(Response.Status.BAD_REQUEST);
+
+    private final Response.Status responseStatus;
+
+    private ClientExceptionType(final Response.Status responseStatus) {
+        this.responseStatus = responseStatus;
+    }
+
+    public static ClientExceptionType fromHeaderValue(final String exceptionTypeHeaderValue) {
+        ClientExceptionType result = null;
+        for (ClientExceptionType type : values()) {
+            if (exceptionTypeHeaderValue.equals(type.getHeaderValue())) {
+                result = type;
+            }
+        }
+
+        if (result == null) {
+            throw new IllegalArgumentException("Unexpected header value: " + exceptionTypeHeaderValue);
+        }
+
+        return result;
+    }
+
+    public String getHeaderValue() {
+        return name();
+    }
+
+    public String getInfoHeaderValue(final String value) {
+        return getHeaderValue() + ":" + value;
+    }
+
+    public Response.Status getResponseStatus() {
+        return responseStatus;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index a8f12f7..0b498d6 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -55,6 +55,10 @@ public enum PolicyType {
         return description;
     }
 
+    public boolean isGlobal() {
+        return name().startsWith("GLOBAL");
+    }
+
     public static PolicyType fromString(final String value) {
         return PolicyType.valueOf(value.toUpperCase());
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReportExecExportFormat.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReportExecExportFormat.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReportExecExportFormat.java
new file mode 100644
index 0000000..fdac43d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReportExecExportFormat.java
@@ -0,0 +1,32 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum ReportExecExportFormat {
+
+    XML,
+    HTML,
+    PDF,
+    RTF,
+    CSV
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/SchemaType.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/SchemaType.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/SchemaType.java
new file mode 100644
index 0000000..673666d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/SchemaType.java
@@ -0,0 +1,68 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+
+@XmlEnum
+public enum SchemaType {
+
+    /**
+     * Standard schema for normal attributes to be stored within syncope.
+     */
+    PLAIN(PlainSchemaTO.class),
+    /**
+     * Derived schema calculated based on other attributes.
+     */
+    DERIVED(DerSchemaTO.class),
+    /**
+     * Virtual schema for attributes fetched from remote resources only.
+     */
+    VIRTUAL(VirSchemaTO.class);
+
+    private final Class<? extends AbstractSchemaTO> toClass;
+
+    SchemaType(final Class<? extends AbstractSchemaTO> toClass) {
+        this.toClass = toClass;
+    }
+
+    public Class<? extends AbstractSchemaTO> getToClass() {
+        return toClass;
+    }
+
+    public static SchemaType fromToClass(final Class<? extends AbstractSchemaTO> toClass) {
+        SchemaType schemaType = null;
+
+        if (PlainSchemaTO.class.equals(toClass)) {
+            schemaType = SchemaType.PLAIN;
+        } else if (DerSchemaTO.class.equals(toClass)) {
+            schemaType = SchemaType.DERIVED;
+        } else if (VirSchemaTO.class.equals(toClass)) {
+            schemaType = SchemaType.VIRTUAL;
+        } else {
+            throw new IllegalArgumentException("Unexpected class: " + toClass.getName());
+        }
+
+        return schemaType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowFormPropertyType.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowFormPropertyType.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowFormPropertyType.java
new file mode 100644
index 0000000..ec2db4a
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowFormPropertyType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum WorkflowFormPropertyType {
+
+    String,
+    Long,
+    Enum,
+    Date,
+    Boolean
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowTasks.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowTasks.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowTasks.java
new file mode 100644
index 0000000..8c487f8
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/types/WorkflowTasks.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common.lib.types;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class WorkflowTasks {
+
+    private List<String> tasks;
+
+    public WorkflowTasks() {
+        this.tasks = new ArrayList<String>();
+    }
+
+    public WorkflowTasks(final Collection<String> tasks) {
+        this();
+        this.tasks.addAll(tasks);
+    }
+
+    public List<String> getTasks() {
+        return tasks;
+    }
+
+    public void setTasks(final List<String> tasks) {
+        this.tasks = tasks;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/AbstractWrappable.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/AbstractWrappable.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/AbstractWrappable.java
new file mode 100644
index 0000000..7ba0afa
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/AbstractWrappable.java
@@ -0,0 +1,46 @@
+/*
+ * 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.common.lib.wrap;
+
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+public abstract class AbstractWrappable<E> extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 1712808704911635170L;
+
+    private E element;
+
+    public static <E, T extends AbstractWrappable<E>> T getInstance(final Class<T> reference, final E element) {
+        try {
+            T instance = reference.newInstance();
+            instance.setElement(element);
+            return instance;
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Could not instantiate " + reference.getName(), e);
+        }
+    }
+
+    public E getElement() {
+        return element;
+    }
+
+    public void setElement(final E element) {
+        this.element = element;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/CorrelationRuleClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/CorrelationRuleClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/CorrelationRuleClass.java
new file mode 100644
index 0000000..6a8997c
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/CorrelationRuleClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "correlationRuleClass")
+@XmlType
+public class CorrelationRuleClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = -6715106427060816725L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/EntitlementTO.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/EntitlementTO.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/EntitlementTO.java
new file mode 100644
index 0000000..62530eb
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/EntitlementTO.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "entitlement")
+@XmlType
+public class EntitlementTO extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 7133614577172038452L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/JobClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/JobClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/JobClass.java
new file mode 100644
index 0000000..b85a6f5
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/JobClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "jobClass")
+@XmlType
+public class JobClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = -1953799905627918822L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/MailTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/MailTemplate.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/MailTemplate.java
new file mode 100644
index 0000000..11e254d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/MailTemplate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "mailTemplate")
+@XmlType
+public class MailTemplate extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 7232619557172031478L;
+
+}


[13/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
[SYNCOPE-620] server logic in, tests missing


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/99369c31
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/99369c31
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/99369c31

Branch: refs/heads/2_0_X
Commit: 99369c3115b9feec77bebff5fbbb2813d72aedf5
Parents: fc8761c
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Jan 8 14:16:57 2015 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Jan 8 14:16:57 2015 +0100

----------------------------------------------------------------------
 .../core/rest/controller/RoleController.java    |  12 +-
 syncope620/common/lib/pom.xml                   |  10 +
 .../syncope/common/lib/AbstractBaseBean.java    |   7 +-
 .../common/lib/AttributableOperations.java      | 508 ++++++++++
 .../lib/SyncopeClientCompositeException.java    |  96 ++
 .../common/lib/SyncopeClientException.java      |  97 ++
 .../common/lib/mod/AbstractAttributableMod.java |  18 +-
 .../syncope/common/lib/mod/MembershipMod.java   |  46 +
 .../syncope/common/lib/mod/ReferenceMod.java    |  53 ++
 .../common/lib/mod/ResourceAssociationMod.java  |  80 ++
 .../apache/syncope/common/lib/mod/RoleMod.java  | 300 ++++++
 .../syncope/common/lib/mod/StatusMod.java       | 108 +++
 .../apache/syncope/common/lib/mod/UserMod.java  | 123 +++
 .../syncope/common/lib/mod/package-info.java    |  23 +
 .../common/lib/report/RoleReportletConf.java    |   2 +-
 .../common/lib/report/UserReportletConf.java    |   2 +-
 .../common/lib/search/OrderByClauseBuilder.java |  45 +
 .../search/RoleFiqlSearchConditionBuilder.java  |  90 ++
 .../syncope/common/lib/search/RoleProperty.java |  29 +
 .../common/lib/search/SearchableFields.java     |  67 ++
 .../syncope/common/lib/search/SpecialAttr.java  |  50 +
 .../SyncopeFiqlSearchConditionBuilder.java      | 110 +++
 .../common/lib/search/SyncopeProperty.java      |  37 +
 .../search/UserFiqlSearchConditionBuilder.java  |  95 ++
 .../syncope/common/lib/search/UserProperty.java |  29 +
 .../common/lib/to/AbstractAttributableTO.java   |  10 +-
 .../syncope/common/lib/to/AbstractExecTO.java   |  87 ++
 .../syncope/common/lib/to/AbstractPolicyTO.java |  89 ++
 .../lib/to/AbstractProvisioningTaskTO.java      | 117 +++
 .../syncope/common/lib/to/AbstractSchemaTO.java |  44 +
 .../syncope/common/lib/to/AbstractTaskTO.java   |  98 ++
 .../syncope/common/lib/to/AccountPolicyTO.java  |  68 ++
 .../syncope/common/lib/to/BulkAction.java       |  70 ++
 .../syncope/common/lib/to/BulkActionResult.java | 134 +++
 .../apache/syncope/common/lib/to/ConfTO.java    |  30 +
 .../syncope/common/lib/to/ConnBundleTO.java     |  95 ++
 .../syncope/common/lib/to/ConnInstanceTO.java   | 177 ++++
 .../syncope/common/lib/to/DerSchemaTO.java      |  39 +
 .../apache/syncope/common/lib/to/ErrorTO.java   |  66 ++
 .../syncope/common/lib/to/EventCategoryTO.java  |  89 ++
 .../apache/syncope/common/lib/to/LoggerTO.java  |  51 ++
 .../syncope/common/lib/to/MappingItemTO.java    | 134 +++
 .../apache/syncope/common/lib/to/MappingTO.java | 120 +++
 .../syncope/common/lib/to/NotificationTO.java   | 175 ++++
 .../common/lib/to/NotificationTaskTO.java       | 105 +++
 .../syncope/common/lib/to/PagedResult.java      |  98 ++
 .../syncope/common/lib/to/PasswordPolicyTO.java |  54 ++
 .../syncope/common/lib/to/PlainSchemaTO.java    | 156 ++++
 .../common/lib/to/PropagationTaskTO.java        | 123 +++
 .../syncope/common/lib/to/PushTaskTO.java       |  49 +
 .../syncope/common/lib/to/ReportExecTO.java     |  40 +
 .../apache/syncope/common/lib/to/ReportTO.java  | 151 +++
 .../syncope/common/lib/to/ResourceTO.java       | 270 ++++++
 .../apache/syncope/common/lib/to/RoleTO.java    |   2 +-
 .../syncope/common/lib/to/SchedTaskTO.java      | 101 ++
 .../common/lib/to/SecurityQuestionTO.java       |  51 ++
 .../syncope/common/lib/to/SyncPolicyTO.java     |  54 ++
 .../syncope/common/lib/to/SyncTaskTO.java       |  59 ++
 .../syncope/common/lib/to/TaskExecTO.java       |  39 +
 .../syncope/common/lib/to/VirSchemaTO.java      |  38 +
 .../common/lib/to/WorkflowFormPropertyTO.java   | 119 +++
 .../syncope/common/lib/to/WorkflowFormTO.java   | 162 ++++
 .../syncope/common/lib/to/package-info.java     |  23 +
 .../syncope/common/lib/types/AuditElements.java |  61 ++
 .../common/lib/types/AuditLoggerName.java       | 222 +++++
 .../common/lib/types/ClientExceptionType.java   | 107 +++
 .../syncope/common/lib/types/PolicyType.java    |   4 +
 .../lib/types/ReportExecExportFormat.java       |  32 +
 .../syncope/common/lib/types/SchemaType.java    |  68 ++
 .../lib/types/WorkflowFormPropertyType.java     |  32 +
 .../syncope/common/lib/types/WorkflowTasks.java |  47 +
 .../common/lib/wrap/AbstractWrappable.java      |  46 +
 .../common/lib/wrap/CorrelationRuleClass.java   |  30 +
 .../syncope/common/lib/wrap/EntitlementTO.java  |  30 +
 .../syncope/common/lib/wrap/JobClass.java       |  30 +
 .../syncope/common/lib/wrap/MailTemplate.java   |  30 +
 .../common/lib/wrap/PropagationActionClass.java |  30 +
 .../common/lib/wrap/PushActionClass.java        |  30 +
 .../common/lib/wrap/ReportletConfClass.java     |  30 +
 .../syncope/common/lib/wrap/ResourceName.java   |  30 +
 .../syncope/common/lib/wrap/SubjectId.java      |  25 +
 .../common/lib/wrap/SyncActionClass.java        |  30 +
 .../syncope/common/lib/wrap/Validator.java      |  30 +
 .../syncope/common/lib/wrap/package-info.java   |  23 +
 syncope620/pom.xml                              | 146 ++-
 syncope620/server/logic/pom.xml                 | 127 +++
 .../syncope/server/logic/AbstractLogic.java     |  58 ++
 .../logic/AbstractResourceAssociator.java       |  37 +
 .../server/logic/AbstractSubjectLogic.java      |  43 +
 .../logic/AbstractTransactionalLogic.java       |  31 +
 .../server/logic/ConfigurationLogic.java        | 157 ++++
 .../syncope/server/logic/ConnectorLogic.java    | 341 +++++++
 .../syncope/server/logic/EntitlementLogic.java  |  58 ++
 .../syncope/server/logic/LoggerLogic.java       | 307 +++++++
 .../server/logic/LogicInvocationHandler.java    | 108 +++
 .../server/logic/NotificationController.java    | 127 +++
 .../syncope/server/logic/PolicyController.java  | 197 ++++
 .../syncope/server/logic/ReportLogic.java       | 348 +++++++
 .../syncope/server/logic/ResourceLogic.java     | 301 ++++++
 .../apache/syncope/server/logic/RoleLogic.java  | 405 ++++++++
 .../syncope/server/logic/SchemaLogic.java       | 328 +++++++
 .../server/logic/SecurityQuestionLogic.java     | 150 +++
 .../apache/syncope/server/logic/TaskLogic.java  | 408 +++++++++
 .../logic/UnresolvedReferenceException.java     |  35 +
 .../apache/syncope/server/logic/UserLogic.java  | 534 +++++++++++
 .../syncope/server/logic/UserWorkflowLogic.java | 131 +++
 .../syncope/server/logic/WorkflowLogic.java     | 115 +++
 .../logic/audit/AuditConnectionFactory.java     | 159 ++++
 .../server/logic/audit/AuditManager.java        | 107 +++
 .../data/AbstractAttributableDataBinder.java    | 918 +++++++++++++++++++
 .../logic/data/ConfigurationDataBinder.java     |  75 ++
 .../logic/data/ConnInstanceDataBinder.java      | 246 +++++
 .../logic/data/NotificationDataBinder.java      |  62 ++
 .../server/logic/data/PolicyDataBinder.java     | 186 ++++
 .../server/logic/data/ReportDataBinder.java     | 175 ++++
 .../server/logic/data/ResourceDataBinder.java   | 389 ++++++++
 .../server/logic/data/RoleDataBinder.java       | 429 +++++++++
 .../server/logic/data/SchemaDataBinder.java     | 163 ++++
 .../logic/data/SecurityQuestionDataBinder.java  |  51 ++
 .../server/logic/data/TaskDataBinder.java       | 338 +++++++
 .../server/logic/data/UserDataBinder.java       | 479 ++++++++++
 .../init/ImplementationClassNamesLoader.java    | 141 +++
 .../server/logic/init/JobInstanceLoader.java    | 287 ++++++
 .../logic/init/WorkflowAdapterLoader.java       |  88 ++
 .../logic/notification/NotificationJob.java     | 280 ++++++
 .../logic/notification/NotificationManager.java | 441 +++++++++
 .../server/logic/report/AbstractReportlet.java  |  66 ++
 .../server/logic/report/ReportException.java    |  32 +
 .../syncope/server/logic/report/ReportJob.java  | 206 +++++
 .../server/logic/report/ReportXMLConst.java     |  44 +
 .../syncope/server/logic/report/Reportlet.java  |  47 +
 .../server/logic/report/ReportletConfClass.java |  32 +
 .../server/logic/report/RoleReportlet.java      | 327 +++++++
 .../server/logic/report/StaticReportlet.java    | 120 +++
 .../server/logic/report/TextSerializer.java     | 101 ++
 .../server/logic/report/UserReportlet.java      | 359 ++++++++
 .../logic/search/SearchCondConverter.java       |  50 +
 .../server/logic/search/SearchCondVisitor.java  | 203 ++++
 .../persistence/api/dao/DuplicateException.java |  35 +
 .../syncope/persistence/api/dao/RoleDAO.java    |   2 +
 .../syncope/persistence/api/dao/UserDAO.java    |   3 +
 .../api/entity/AttributableUtil.java            |   5 +
 .../api/entity/AttributableUtilFactory.java     |  33 +
 .../syncope/persistence/api/entity/Entity.java  |   4 +-
 .../persistence/api/entity/EntityFactory.java   |   2 +
 .../api/entity/ExternalResource.java            |   2 +-
 .../persistence/api/entity/role/Role.java       |   4 +-
 .../persistence/api/entity/task/TaskUtil.java   |  36 +
 .../api/entity/task/TaskUtilFactory.java        |  33 +
 syncope620/server/persistence-jpa/pom.xml       |   2 +-
 .../persistence/jpa/dao/AbstractSubjectDAO.java |  10 +-
 .../syncope/persistence/jpa/dao/JPAConfDAO.java |   7 +-
 .../syncope/persistence/jpa/dao/JPARoleDAO.java |  36 +-
 .../jpa/dao/JPASubjectSearchDAO.java            |  13 +-
 .../syncope/persistence/jpa/dao/JPAUserDAO.java |  69 +-
 .../persistence/jpa/entity/AbstractEntity.java  |   5 +-
 .../jpa/entity/JPAAttributableUtil.java         | 103 +--
 .../jpa/entity/JPAEntityFactory.java            |   9 +-
 .../persistence/jpa/entity/JPAPushPolicy.java   |   2 +-
 .../jpa/entity/JPASecurityQuestion.java         |   2 +-
 .../jpa/entity/JPAttributableUtilFactory.java   |  85 ++
 .../jpa/entity/conf/JPACPlainAttr.java          |   2 +-
 .../entity/conf/JPACPlainAttrUniqueValue.java   |   2 +-
 .../jpa/entity/conf/JPACPlainAttrValue.java     |   2 +-
 .../persistence/jpa/entity/conf/JPAConf.java    |   2 +-
 .../entity/membership/JPAMDerAttrTemplate.java  |   2 +-
 .../membership/JPAMPlainAttrTemplate.java       |   2 +-
 .../entity/membership/JPAMVirAttrTemplate.java  |   2 +-
 .../jpa/entity/role/JPARDerAttrTemplate.java    |   2 +-
 .../jpa/entity/role/JPARPlainAttrTemplate.java  |   2 +-
 .../jpa/entity/role/JPARVirAttrTemplate.java    |   2 +-
 .../persistence/jpa/entity/role/JPARole.java    |  16 +-
 .../jpa/entity/task/JPASchedTask.java           |   2 +-
 .../jpa/entity/task/JPATaskUtil.java            | 132 +++
 .../jpa/entity/task/JPATaskUtilFactory.java     |  91 ++
 .../persistence/jpa/entity/user/JPAUser.java    |   8 +-
 .../entity/ConnInstanceValidator.java           |   2 +-
 .../syncope/persistence/jpa/AbstractTest.java   |   4 +
 .../persistence/jpa/entity/AttrTest.java        |  14 +-
 .../persistence/jpa/entity/ConfTest.java        |   4 +-
 .../persistence/jpa/entity/DerSchemaTest.java   |   4 +-
 .../persistence/jpa/entity/PlainSchemaTest.java |   2 +-
 .../persistence/jpa/entity/VirSchemaTest.java   |   4 +-
 .../persistence/jpa/relationship/AttrTest.java  |   3 +-
 .../jpa/relationship/DerSchemaTest.java         |   3 +-
 .../jpa/relationship/PlainSchemaTest.java       |   7 +-
 .../jpa/relationship/SecurityQuestionTest.java  |   3 +
 .../src/test/resources/content.xml              |  26 +-
 .../src/test/resources/persistenceTestEnv.xml   |   5 +
 syncope620/server/pom.xml                       |   5 +-
 .../api/AttributableTransformer.java            |  33 +
 .../provisioning/api/ConnIdBundleManager.java   | 284 ++++++
 .../provisioning/api/ProvisioningManager.java   |  42 +
 .../api/RoleProvisioningManager.java            |  37 +
 .../syncope/provisioning/api/URIUtil.java       |  61 ++
 .../api/UserProvisioningManager.java            |  57 ++
 .../provisioning/api/WorkflowResult.java        |  87 ++
 .../provisioning/api/cache/VirAttrCache.java    |  65 ++
 .../provisioning/api/cache/VirAttrCacheKey.java |  79 ++
 .../api/cache/VirAttrCacheValue.java            |  86 ++
 .../api/propagation/PropagationByResource.java  | 365 ++++++++
 .../api/propagation/PropagationException.java   |  51 ++
 .../api/propagation/PropagationManager.java     | 248 +++++
 .../api/propagation/PropagationReporter.java    |  58 ++
 .../propagation/PropagationTaskExecutor.java    |  77 ++
 .../api/sync/SyncCorrelationRule.java           |  36 +
 syncope620/server/provisioning-common/pom.xml   |  44 +
 .../common/cache/DisabledVirAttrCache.java      |  52 ++
 .../common/cache/MemoryVirAttrCache.java        | 151 +++
 .../server/security/SecureRandomUtil.java       |  44 -
 .../security/UnauthorizedRoleException.java     |  42 +
 syncope620/server/utils/pom.xml                 |  17 +-
 .../syncope/server/utils/ConnObjectUtil.java    | 764 +++++++++++++++
 .../syncope/server/utils/ExceptionUtil.java     |  47 +
 .../InvalidPasswordPolicySpecException.java     |  37 +
 .../syncope/server/utils/MappingUtil.java       | 736 +++++++++++++++
 .../syncope/server/utils/PasswordGenerator.java | 320 +++++++
 .../syncope/server/utils/SecureRandomUtil.java  |  48 +
 .../apache/syncope/server/utils/URIUtil.java    |  61 --
 .../serialization/UnwrappedObjectMapper.java    |  95 ++
 syncope620/server/workflow-api/pom.xml          |  44 +
 .../workflow/api/RoleWorkflowAdapter.java       |  71 ++
 .../workflow/api/UserWorkflowAdapter.java       | 151 +++
 .../server/workflow/api/WorkflowAdapter.java    | 111 +++
 .../workflow/api/WorkflowDefinitionFormat.java  |  29 +
 .../server/workflow/api/WorkflowException.java  |  51 ++
 .../workflow/api/WorkflowInstanceLoader.java    |  28 +
 .../server/workflow/api/package-info.java       |  19 +
 228 files changed, 22689 insertions(+), 280 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java b/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java
index 3a0f9d6..9b1220e 100644
--- a/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java
+++ b/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java
@@ -18,13 +18,10 @@
  */
 package org.apache.syncope.core.rest.controller;
 
-import static org.apache.syncope.core.rest.controller.AbstractController.LOG;
-
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -36,12 +33,10 @@ import org.apache.syncope.common.to.RoleTO;
 import org.apache.syncope.common.types.ClientExceptionType;
 import org.apache.syncope.common.SyncopeClientException;
 import org.apache.syncope.common.to.PropagationStatus;
-import org.apache.syncope.core.provisioning.ProvisioningManager;
 import org.apache.syncope.core.provisioning.RoleProvisioningManager;
 import org.apache.syncope.common.reqres.BulkAction;
 import org.apache.syncope.common.reqres.BulkActionResult;
 import org.apache.syncope.common.types.SubjectType;
-import org.apache.syncope.core.persistence.beans.PropagationTask;
 import org.apache.syncope.core.persistence.beans.role.SyncopeRole;
 import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
 import org.apache.syncope.core.persistence.dao.SubjectSearchDAO;
@@ -49,13 +44,10 @@ import org.apache.syncope.core.persistence.dao.NotFoundException;
 import org.apache.syncope.core.persistence.dao.RoleDAO;
 import org.apache.syncope.core.persistence.dao.UserDAO;
 import org.apache.syncope.core.persistence.dao.search.OrderByClause;
-import org.apache.syncope.core.propagation.PropagationException;
-import org.apache.syncope.core.propagation.PropagationReporter;
 import org.apache.syncope.core.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.propagation.impl.PropagationManager;
 import org.apache.syncope.core.rest.data.AttributableTransformer;
 import org.apache.syncope.core.rest.data.RoleDataBinder;
-import org.apache.syncope.core.util.ApplicationContextProvider;
 import org.apache.syncope.core.util.EntitlementUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -94,9 +86,9 @@ public class RoleController extends AbstractSubjectController<RoleTO, RoleMod> {
     protected AttributableTransformer attrTransformer;
 
     @Resource(name = "anonymousUser")
-    private String anonymousUser;
+    protected String anonymousUser;
 
-    @Resource(name = "roleProvisioningManager")
+    @Autowired
     protected RoleProvisioningManager provisioningManager;
 
     @PreAuthorize("hasAnyRole('ROLE_READ', T(org.apache.syncope.common.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/pom.xml b/syncope620/common/lib/pom.xml
index d52f136..ad51d98 100644
--- a/syncope620/common/lib/pom.xml
+++ b/syncope620/common/lib/pom.xml
@@ -35,6 +35,16 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>javax.ws.rs</groupId>
+      <artifactId>javax.ws.rs-api</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-extension-search</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-annotations</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AbstractBaseBean.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AbstractBaseBean.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AbstractBaseBean.java
index 28bd4bd..8a6d9c4 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AbstractBaseBean.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AbstractBaseBean.java
@@ -19,15 +19,20 @@
 package org.apache.syncope.common.lib;
 
 import java.io.Serializable;
+import javax.xml.bind.annotation.XmlSeeAlso;
 import javax.xml.bind.annotation.XmlType;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
 
 @XmlType
 // Reporting here only classes used via PagedResult
-//@XmlSeeAlso({ AbstractTaskTO.class, ReportTO.class, RoleTO.class, UserTO.class })
+@XmlSeeAlso({ AbstractTaskTO.class, ReportTO.class, RoleTO.class, UserTO.class })
 public abstract class AbstractBaseBean implements Serializable {
 
     private static final long serialVersionUID = 3119542005279892164L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AttributableOperations.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AttributableOperations.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AttributableOperations.java
new file mode 100644
index 0000000..983cee4
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/AttributableOperations.java
@@ -0,0 +1,508 @@
+/*
+ * 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.common.lib;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.ReferenceMod;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+
+/**
+ * Utility class for manipulating classes extending AbstractAttributableTO and AbstractAttributableMod.
+ *
+ * @see AbstractAttributableTO
+ * @see AbstractAttributableMod
+ */
+public final class AttributableOperations {
+
+    private AttributableOperations() {
+        // empty constructor for static utility classes
+    }
+
+    private static void populate(final Map<String, AttrTO> updatedAttrs,
+            final Map<String, AttrTO> originalAttrs, final AbstractAttributableMod result) {
+
+        populate(updatedAttrs, originalAttrs, result, false);
+    }
+
+    private static void populate(final Map<String, AttrTO> updatedAttrs,
+            final Map<String, AttrTO> originalAttrs, final AbstractAttributableMod result,
+            final boolean virtuals) {
+
+        for (Map.Entry<String, AttrTO> entry : updatedAttrs.entrySet()) {
+            AttrMod mod = new AttrMod();
+            mod.setSchema(entry.getKey());
+
+            Set<String> updatedValues = new HashSet<String>(entry.getValue().getValues());
+
+            Set<String> originalValues = originalAttrs.containsKey(entry.getKey())
+                    ? new HashSet<String>(originalAttrs.get(entry.getKey()).getValues())
+                    : Collections.<String>emptySet();
+
+            if (!originalAttrs.containsKey(entry.getKey())) {
+                // SYNCOPE-459: take care of user virtual attributes without any value
+                updatedValues.remove("");
+                mod.getValuesToBeAdded().addAll(new ArrayList<String>(updatedValues));
+
+                if (virtuals) {
+                    result.getVirAttrsToUpdate().add(mod);
+                } else {
+                    result.getAttrsToUpdate().add(mod);
+                }
+            } else if (!updatedValues.equals(originalValues)) {
+                // avoid unwanted inputs
+                updatedValues.remove("");
+                if (!entry.getValue().isReadonly()) {
+                    mod.getValuesToBeAdded().addAll(updatedValues);
+
+                    if (!mod.isEmpty()) {
+                        if (virtuals) {
+                            result.getVirAttrsToRemove().add(mod.getSchema());
+                        } else {
+                            result.getAttrsToRemove().add(mod.getSchema());
+                        }
+                    }
+                }
+
+                mod.getValuesToBeRemoved().addAll(originalValues);
+
+                if (!mod.isEmpty()) {
+                    if (virtuals) {
+                        result.getVirAttrsToUpdate().add(mod);
+                    } else {
+                        result.getAttrsToUpdate().add(mod);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void diff(
+            final AbstractAttributableTO updated,
+            final AbstractAttributableTO original,
+            final AbstractAttributableMod result,
+            final boolean incremental) {
+
+        // 1. check same id
+        if (updated.getKey() != original.getKey()) {
+            throw new IllegalArgumentException("AttributableTO's id must be the same");
+        }
+        result.setKey(updated.getKey());
+
+        // 2. attributes
+        Map<String, AttrTO> updatedAttrs = new HashMap<>(updated.getAttrMap());
+        Map<String, AttrTO> originalAttrs = new HashMap<>(original.getAttrMap());
+
+        Set<String> originalAttrNames = new HashSet<>(originalAttrs.keySet());
+        originalAttrNames.removeAll(updatedAttrs.keySet());
+
+        if (!incremental) {
+            result.getAttrsToRemove().clear();
+            result.getAttrsToRemove().addAll(originalAttrNames);
+        }
+
+        Set<String> emptyUpdatedAttrs = new HashSet<>();
+        for (Map.Entry<String, AttrTO> entry : updatedAttrs.entrySet()) {
+            if (entry.getValue().getValues() == null || entry.getValue().getValues().isEmpty()) {
+
+                emptyUpdatedAttrs.add(entry.getKey());
+            }
+        }
+        for (String emptyUpdatedAttr : emptyUpdatedAttrs) {
+            updatedAttrs.remove(emptyUpdatedAttr);
+            result.getAttrsToRemove().add(emptyUpdatedAttr);
+        }
+
+        populate(updatedAttrs, originalAttrs, result);
+
+        // 3. derived attributes
+        updatedAttrs = updated.getDerAttrMap();
+        originalAttrs = original.getDerAttrMap();
+
+        originalAttrNames = new HashSet<>(originalAttrs.keySet());
+        originalAttrNames.removeAll(updatedAttrs.keySet());
+
+        if (!incremental) {
+            result.getDerAttrsToRemove().clear();
+            result.getDerAttrsToRemove().addAll(originalAttrNames);
+        }
+
+        Set<String> updatedAttrNames = new HashSet<>(updatedAttrs.keySet());
+        updatedAttrNames.removeAll(originalAttrs.keySet());
+        result.getDerAttrsToAdd().clear();
+        result.getDerAttrsToAdd().addAll(updatedAttrNames);
+
+        // 4. virtual attributes
+        updatedAttrs = updated.getVirAttrMap();
+        originalAttrs = original.getVirAttrMap();
+
+        originalAttrNames = new HashSet<>(originalAttrs.keySet());
+        originalAttrNames.removeAll(updatedAttrs.keySet());
+
+        if (!incremental) {
+            result.getVirAttrsToRemove().clear();
+            result.getVirAttrsToRemove().addAll(originalAttrNames);
+        }
+
+        populate(updatedAttrs, originalAttrs, result, true);
+
+        // 5. resources
+        if (original instanceof AbstractSubjectTO && updated instanceof AbstractSubjectTO
+                && result instanceof AbstractSubjectMod) {
+
+            Set<String> updatedRes = new HashSet<>(((AbstractSubjectTO) updated).getResources());
+            Set<String> originalRes = new HashSet<>(((AbstractSubjectTO) original).getResources());
+
+            updatedRes.removeAll(originalRes);
+            ((AbstractSubjectMod) result).getResourcesToAdd().clear();
+            ((AbstractSubjectMod) result).getResourcesToAdd().addAll(updatedRes);
+
+            originalRes.removeAll(((AbstractSubjectTO) updated).getResources());
+
+            if (!incremental) {
+                ((AbstractSubjectMod) result).getResourcesToRemove().clear();
+                ((AbstractSubjectMod) result).getResourcesToRemove().addAll(originalRes);
+            }
+        }
+    }
+
+    /**
+     * Calculate modifications needed by first in order to be equal to second.
+     *
+     * @param updated updated UserTO
+     * @param original original UserTO
+     * @return UserMod containing differences
+     */
+    public static UserMod diff(final UserTO updated, final UserTO original) {
+        return diff(updated, original, false);
+    }
+
+    /**
+     * Calculate modifications needed by first in order to be equal to second.
+     *
+     * @param updated updated UserTO
+     * @param original original UserTO
+     * @param incremental perform incremental diff (without removing existing info)
+     * @return UserMod containing differences
+     */
+    public static UserMod diff(final UserTO updated, final UserTO original, final boolean incremental) {
+        UserMod result = new UserMod();
+
+        diff(updated, original, result, incremental);
+
+        // 1. password
+        if (updated.getPassword() != null && (original.getPassword() == null
+                || !original.getPassword().equals(updated.getPassword()))) {
+
+            result.setPassword(updated.getPassword());
+        }
+
+        // 2. username
+        if (original.getUsername() != null && !original.getUsername().equals(updated.getUsername())) {
+            result.setUsername(updated.getUsername());
+        }
+
+        // 3. security question / answer
+        if (updated.getSecurityQuestion() == null) {
+            result.setSecurityQuestion(null);
+            result.setSecurityAnswer(null);
+        } else if (!updated.getSecurityQuestion().equals(original.getSecurityQuestion())
+                || StringUtils.isNotBlank(updated.getSecurityAnswer())) {
+
+            result.setSecurityQuestion(updated.getSecurityQuestion());
+            result.setSecurityAnswer(updated.getSecurityAnswer());
+        }
+
+        // 4. memberships
+        Map<Long, MembershipTO> updatedMembs = updated.getMembershipMap();
+        Map<Long, MembershipTO> originalMembs = original.getMembershipMap();
+
+        for (Map.Entry<Long, MembershipTO> entry : updatedMembs.entrySet()) {
+            MembershipMod membMod = new MembershipMod();
+            membMod.setRole(entry.getValue().getRoleId());
+
+            if (originalMembs.containsKey(entry.getKey())) {
+                // if memberships are actually same, just make the isEmpty() call below succeed
+                if (entry.getValue().equals(originalMembs.get(entry.getKey()))) {
+                    membMod.setRole(0);
+                } else {
+                    diff(entry.getValue(), originalMembs.get(entry.getKey()), membMod, false);
+                }
+            } else {
+                for (AttrTO attr : entry.getValue().getPlainAttrs()) {
+                    AttrMod attrMod = new AttrMod();
+                    attrMod.setSchema(attr.getSchema());
+                    attrMod.getValuesToBeAdded().addAll(attr.getValues());
+
+                    if (!attrMod.isEmpty()) {
+                        membMod.getAttrsToUpdate().add(attrMod);
+                        membMod.getAttrsToRemove().add(attrMod.getSchema());
+                    }
+                }
+                for (AttrTO attr : entry.getValue().getDerAttrs()) {
+                    membMod.getDerAttrsToAdd().add(attr.getSchema());
+                }
+                for (AttrTO attr : entry.getValue().getVirAttrs()) {
+                    AttrMod attrMod = new AttrMod();
+                    attrMod.setSchema(attr.getSchema());
+                    attrMod.getValuesToBeAdded().addAll(attr.getValues());
+
+                    if (!attrMod.isEmpty()) {
+                        membMod.getVirAttrsToUpdate().add(attrMod);
+                        membMod.getAttrsToRemove().add(attrMod.getSchema());
+                    }
+                }
+            }
+
+            if (!membMod.isEmpty()) {
+                result.getMembershipsToAdd().add(membMod);
+            }
+        }
+
+        if (!incremental) {
+            Set<Long> originalRoles = new HashSet<>(originalMembs.keySet());
+            originalRoles.removeAll(updatedMembs.keySet());
+            for (Long roleId : originalRoles) {
+                result.getMembershipsToRemove().add(originalMembs.get(roleId).getKey());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Calculate modifications needed by first in order to be equal to second.
+     *
+     * @param updated updated RoleTO
+     * @param original original RoleTO
+     * @return RoleMod containing differences
+     */
+    public static RoleMod diff(final RoleTO updated, final RoleTO original) {
+        return diff(updated, original, false);
+    }
+
+    /**
+     * Calculate modifications needed by first in order to be equal to second.
+     *
+     * @param updated updated RoleTO
+     * @param original original RoleTO
+     * @param incremental perform incremental diff (without removing existing info)
+     * @return RoleMod containing differences
+     */
+    public static RoleMod diff(final RoleTO updated, final RoleTO original, final boolean incremental) {
+        RoleMod result = new RoleMod();
+
+        diff(updated, original, result, incremental);
+
+        // 1. inheritance
+        result.setInheritOwner(updated.isInheritOwner());
+        result.setInheritTemplates(updated.isInheritTemplates());
+        result.setInheritAccountPolicy(updated.isInheritAccountPolicy());
+        result.setInheritPasswordPolicy(updated.isInheritPasswordPolicy());
+        result.setInheritPlainAttrs(updated.isInheritAttrs());
+        result.setInheritDerAttrs(updated.isInheritDerAttrs());
+        result.setInheritVirAttrs(updated.isInheritVirAttrs());
+
+        // 2. policies
+        result.setAccountPolicy(new ReferenceMod(updated.getAccountPolicy()));
+        result.setPasswordPolicy(new ReferenceMod(updated.getPasswordPolicy()));
+
+        // 3. name
+        if (!original.getName().equals(updated.getName())) {
+            result.setName(updated.getName());
+        }
+
+        // 4. entitlements
+        Set<String> updatedEnts = new HashSet<>(updated.getEntitlements());
+        Set<String> originalEnts = new HashSet<>(original.getEntitlements());
+        if (updatedEnts.equals(originalEnts)) {
+            result.setModEntitlements(false);
+            result.getEntitlements().clear();
+        } else {
+            result.setModEntitlements(true);
+            result.getEntitlements().addAll(updated.getEntitlements());
+        }
+
+        // 5. templates
+        Set<String> updatedTemplates = new HashSet<>(updated.getRAttrTemplates());
+        Set<String> originalTemplates = new HashSet<>(original.getRAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModRAttrTemplates(false);
+            result.getRPlainAttrTemplates().clear();
+        } else {
+            result.setModRAttrTemplates(true);
+            result.getRPlainAttrTemplates().addAll(updated.getRAttrTemplates());
+        }
+        updatedTemplates = new HashSet<>(updated.getRDerAttrTemplates());
+        originalTemplates = new HashSet<>(original.getRDerAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModRDerAttrTemplates(false);
+            result.getRDerAttrTemplates().clear();
+        } else {
+            result.setModRDerAttrTemplates(true);
+            result.getRDerAttrTemplates().addAll(updated.getRDerAttrTemplates());
+        }
+        updatedTemplates = new HashSet<>(updated.getRVirAttrTemplates());
+        originalTemplates = new HashSet<>(original.getRVirAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModRVirAttrTemplates(false);
+            result.getRVirAttrTemplates().clear();
+        } else {
+            result.setModRVirAttrTemplates(true);
+            result.getRVirAttrTemplates().addAll(updated.getRVirAttrTemplates());
+        }
+        updatedTemplates = new HashSet<>(updated.getMAttrTemplates());
+        originalTemplates = new HashSet<>(original.getMAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModMAttrTemplates(false);
+            result.getMPlainAttrTemplates().clear();
+        } else {
+            result.setModMAttrTemplates(true);
+            result.getMPlainAttrTemplates().addAll(updated.getMAttrTemplates());
+        }
+        updatedTemplates = new HashSet<>(updated.getMDerAttrTemplates());
+        originalTemplates = new HashSet<>(original.getMDerAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModMDerAttrTemplates(false);
+            result.getMDerAttrTemplates().clear();
+        } else {
+            result.setModMDerAttrTemplates(true);
+            result.getMDerAttrTemplates().addAll(updated.getMDerAttrTemplates());
+        }
+        updatedTemplates = new HashSet<>(updated.getMVirAttrTemplates());
+        originalTemplates = new HashSet<>(original.getMVirAttrTemplates());
+        if (updatedTemplates.equals(originalTemplates)) {
+            result.setModMVirAttrTemplates(false);
+            result.getMVirAttrTemplates().clear();
+        } else {
+            result.setModMVirAttrTemplates(true);
+            result.getMVirAttrTemplates().addAll(updated.getMVirAttrTemplates());
+        }
+
+        // 6. owner
+        result.setUserOwner(new ReferenceMod(updated.getUserOwner()));
+        result.setRoleOwner(new ReferenceMod(updated.getRoleOwner()));
+
+        return result;
+    }
+
+    private static List<AttrTO> getUpdateValues(final Map<String, AttrTO> attrs,
+            final Set<String> attrsToBeRemoved, final Set<AttrMod> attrsToBeUpdated) {
+
+        Map<String, AttrTO> rwattrs = new HashMap<>(attrs);
+        for (String attrName : attrsToBeRemoved) {
+            rwattrs.remove(attrName);
+        }
+        for (AttrMod attrMod : attrsToBeUpdated) {
+            if (rwattrs.containsKey(attrMod.getSchema())) {
+                AttrTO attrTO = rwattrs.get(attrMod.getSchema());
+                attrTO.getValues().removeAll(attrMod.getValuesToBeRemoved());
+                attrTO.getValues().addAll(attrMod.getValuesToBeAdded());
+            } else {
+                AttrTO attrTO = new AttrTO();
+                attrTO.setSchema(attrMod.getSchema());
+                attrTO.getValues().addAll(attrMod.getValuesToBeAdded());
+
+                rwattrs.put(attrMod.getSchema(), attrTO);
+            }
+        }
+
+        return new ArrayList<>(rwattrs.values());
+    }
+
+    private static <T extends AbstractAttributableTO, K extends AbstractAttributableMod> void apply(final T to,
+            final K mod, final T result) {
+
+        // 1. attributes
+        result.getPlainAttrs().addAll(getUpdateValues(to.getAttrMap(),
+                mod.getAttrsToRemove(), mod.getAttrsToUpdate()));
+
+        // 2. derived attributes
+        Map<String, AttrTO> attrs = to.getDerAttrMap();
+        for (String attrName : mod.getDerAttrsToRemove()) {
+            attrs.remove(attrName);
+        }
+        for (String attrName : mod.getDerAttrsToAdd()) {
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(attrName);
+
+            attrs.put(attrName, attrTO);
+        }
+        result.getDerAttrs().addAll(attrs.values());
+
+        // 3. virtual attributes
+        result.getVirAttrs().addAll(getUpdateValues(to.getVirAttrMap(),
+                mod.getVirAttrsToRemove(), mod.getVirAttrsToUpdate()));
+
+        // 4. resources
+        if (result instanceof AbstractSubjectTO && mod instanceof AbstractSubjectMod) {
+            ((AbstractSubjectTO) result).getResources().removeAll(((AbstractSubjectMod) mod).getResourcesToRemove());
+            ((AbstractSubjectTO) result).getResources().addAll(((AbstractSubjectMod) mod).getResourcesToAdd());
+        }
+    }
+
+    public static UserTO apply(final UserTO userTO, final UserMod userMod) {
+        // 1. check same id
+        if (userTO.getKey() != userMod.getKey()) {
+            throw new IllegalArgumentException("UserTO and UserMod ids must be the same");
+        }
+
+        UserTO result = SerializationUtils.clone(userTO);
+        apply(userTO, userMod, result);
+
+        // 1. password
+        result.setPassword(userMod.getPassword());
+
+        // 2. username
+        if (userMod.getUsername() != null) {
+            result.setUsername(userMod.getUsername());
+        }
+        // 3. memberships
+        Map<Long, MembershipTO> membs = result.getMembershipMap();
+        for (Long membId : userMod.getMembershipsToRemove()) {
+            result.getMemberships().remove(membs.get(membId));
+        }
+        for (MembershipMod membMod : userMod.getMembershipsToAdd()) {
+            MembershipTO membTO = new MembershipTO();
+            membTO.setRoleId(membMod.getRole());
+
+            apply(membTO, membMod, membTO);
+        }
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java
new file mode 100644
index 0000000..7322cc8
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientCompositeException.java
@@ -0,0 +1,96 @@
+/*
+ * 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.common.lib;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+
+public class SyncopeClientCompositeException extends SyncopeClientException {
+
+    private static final long serialVersionUID = 7882118041134372129L;
+
+    private final Set<SyncopeClientException> exceptions = new HashSet<>();
+
+    protected SyncopeClientCompositeException() {
+        super(ClientExceptionType.Composite);
+    }
+
+    public boolean hasExceptions() {
+        return !exceptions.isEmpty();
+    }
+
+    public boolean hasException(final ClientExceptionType exceptionType) {
+        return getException(exceptionType) != null;
+    }
+
+    public SyncopeClientException getException(final ClientExceptionType exceptionType) {
+        boolean found = false;
+        SyncopeClientException syncopeClientException = null;
+        for (Iterator<SyncopeClientException> itor = exceptions.iterator(); itor.hasNext() && !found;) {
+            syncopeClientException = itor.next();
+            if (syncopeClientException.getType().equals(exceptionType)) {
+                found = true;
+            }
+        }
+
+        return found
+                ? syncopeClientException
+                : null;
+    }
+
+    public Set<SyncopeClientException> getExceptions() {
+        return exceptions;
+    }
+
+    public boolean addException(final SyncopeClientException exception) {
+        if (exception.getType() == null) {
+            throw new IllegalArgumentException(exception + " does not have the right "
+                    + ClientExceptionType.class.getName() + " set");
+        }
+
+        return exceptions.add(exception);
+    }
+
+    @Override
+    public String getMessage() {
+        StringBuilder message = new StringBuilder();
+
+        message.append("{");
+        Iterator<SyncopeClientException> iter = getExceptions().iterator();
+        while (iter.hasNext()) {
+            SyncopeClientException e = iter.next();
+            message.append("[");
+            message.append(e.getMessage());
+            message.append("]");
+            if (iter.hasNext()) {
+                message.append(", ");
+            }
+        }
+        message.append("}");
+
+        return message.toString();
+    }
+
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientException.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientException.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientException.java
new file mode 100644
index 0000000..2243835
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeClientException.java
@@ -0,0 +1,97 @@
+/*
+ * 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.common.lib;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+
+public class SyncopeClientException extends RuntimeException {
+
+    private static final long serialVersionUID = 3380920886511913475L;
+
+    private ClientExceptionType type;
+
+    private final List<String> elements = new ArrayList<>();
+
+    public static SyncopeClientException build(final ClientExceptionType type) {
+        if (type == ClientExceptionType.Composite) {
+            throw new IllegalArgumentException("Composite exceptions must be obtained via buildComposite()");
+        }
+        return new SyncopeClientException(type);
+    }
+
+    public static SyncopeClientCompositeException buildComposite() {
+        return new SyncopeClientCompositeException();
+    }
+
+    protected SyncopeClientException(final ClientExceptionType type) {
+        super();
+        setType(type);
+    }
+
+    public boolean isComposite() {
+        return getType() == ClientExceptionType.Composite;
+    }
+
+    public SyncopeClientCompositeException asComposite() {
+        if (!isComposite()) {
+            throw new IllegalArgumentException("This is not a composite exception");
+        }
+
+        return (SyncopeClientCompositeException) this;
+    }
+
+    public ClientExceptionType getType() {
+        return type;
+    }
+
+    public final void setType(final ClientExceptionType type) {
+        this.type = type;
+    }
+
+    public List<String> getElements() {
+        return elements;
+    }
+
+    public boolean isEmpty() {
+        return elements.isEmpty();
+    }
+
+    public int size() {
+        return elements.size();
+    }
+
+    @Override
+    public String getMessage() {
+        StringBuilder message = new StringBuilder();
+
+        message.append(getType());
+        message.append(" ");
+        message.append(getElements());
+
+        return message.toString();
+    }
+
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/AbstractAttributableMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/AbstractAttributableMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/AbstractAttributableMod.java
index 7339565..5d6b982 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/AbstractAttributableMod.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/AbstractAttributableMod.java
@@ -38,9 +38,9 @@ public abstract class AbstractAttributableMod extends AbstractBaseBean {
 
     protected long key;
 
-    protected final Set<AttrMod> attrsToUpdate = new HashSet<>();
+    protected final Set<AttrMod> plainAttrsToUpdate = new HashSet<>();
 
-    protected final Set<String> attrsToRemove = new HashSet<>();
+    protected final Set<String> plainAttrsToRemove = new HashSet<>();
 
     protected final Set<String> derAttrsToAdd = new HashSet<>();
 
@@ -58,18 +58,18 @@ public abstract class AbstractAttributableMod extends AbstractBaseBean {
         this.key = key;
     }
 
-    @XmlElementWrapper(name = "attributesToRemove")
+    @XmlElementWrapper(name = "plainAttrsToRemove")
     @XmlElement(name = "attribute")
-    @JsonProperty("attributesToRemove")
+    @JsonProperty("plainAttrsToRemove")
     public Set<String> getAttrsToRemove() {
-        return attrsToRemove;
+        return plainAttrsToRemove;
     }
 
-    @XmlElementWrapper(name = "attributesToUpdate")
+    @XmlElementWrapper(name = "plainAttrsToUpdate")
     @XmlElement(name = "attributeMod")
-    @JsonProperty("attributesToUpdate")
+    @JsonProperty("plainAttrsToUpdate")
     public Set<AttrMod> getAttrsToUpdate() {
-        return attrsToUpdate;
+        return plainAttrsToUpdate;
     }
 
     @XmlElementWrapper(name = "derAttrsToAdd")
@@ -104,7 +104,7 @@ public abstract class AbstractAttributableMod extends AbstractBaseBean {
      * @return true is all backing Sets are empty.
      */
     public boolean isEmpty() {
-        return attrsToUpdate.isEmpty() && attrsToRemove.isEmpty()
+        return plainAttrsToUpdate.isEmpty() && plainAttrsToRemove.isEmpty()
                 && derAttrsToAdd.isEmpty() && derAttrsToRemove.isEmpty()
                 && virAttrsToUpdate.isEmpty() && virAttrsToRemove.isEmpty();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/MembershipMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/MembershipMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/MembershipMod.java
new file mode 100644
index 0000000..bdcfcbf
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/MembershipMod.java
@@ -0,0 +1,46 @@
+/*
+ * 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.common.lib.mod;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement
+@XmlType
+public class MembershipMod extends AbstractAttributableMod {
+
+    private static final long serialVersionUID = 2511869129977331525L;
+
+    private long role;
+
+    public long getRole() {
+        return role;
+    }
+
+    public void setRole(final long role) {
+        this.role = role;
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty() && role == 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ReferenceMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ReferenceMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ReferenceMod.java
new file mode 100644
index 0000000..e8356c2
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ReferenceMod.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common.lib.mod;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+/**
+ * This class is used to specify the willing to modify an external reference id. Use 'null' ReferenceMod to keep the
+ * current reference id; use a ReferenceMod with a null id to try to reset the reference id; use a ReferenceMod with a
+ * not null id to specify a new reference id.
+ */
+@XmlRootElement(name = "referenceMod")
+@XmlType
+public class ReferenceMod extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -4188817853738067677L;
+
+    private Long key;
+
+    public ReferenceMod() {
+        this.key = null;
+    }
+
+    public ReferenceMod(final Long key) {
+        this.key = key;
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+    public void setKey(final Long key) {
+        this.key = key;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ResourceAssociationMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ResourceAssociationMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ResourceAssociationMod.java
new file mode 100644
index 0000000..6312349
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/ResourceAssociationMod.java
@@ -0,0 +1,80 @@
+/*
+ * 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.common.lib.mod;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.wrap.ResourceName;
+
+/**
+ * This class is used to specify the willing to create associations between user and external references.
+ * Password can be provided if required by an assign or provisioning operation.
+ *
+ * @see org.apache.syncope.common.types.ResourceAssociationActionType
+ */
+@XmlRootElement(name = "resourceAssociationMod")
+@XmlType
+public class ResourceAssociationMod extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -4188817853738067678L;
+
+    /**
+     * Target external resources.
+     */
+    private final List<ResourceName> targetResources = new ArrayList<ResourceName>();
+
+    /**
+     * Indicate the willing to change password on target external resources.
+     */
+    private boolean changePwd;
+
+    /**
+     * Indicate the new password to be provisioned on target external resources.
+     */
+    private String password;
+
+    @XmlElementWrapper(name = "resources")
+    @XmlElement(name = "resource")
+    @JsonProperty("resources")
+    public List<ResourceName> getTargetResources() {
+        return targetResources;
+    }
+
+    public boolean isChangePwd() {
+        return changePwd;
+    }
+
+    public void setChangePwd(boolean changePwd) {
+        this.changePwd = changePwd;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/RoleMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/RoleMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/RoleMod.java
new file mode 100644
index 0000000..b1b8f4f
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/RoleMod.java
@@ -0,0 +1,300 @@
+/*
+ * 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.common.lib.mod;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "roleMod")
+@XmlType
+public class RoleMod extends AbstractSubjectMod {
+
+    private static final long serialVersionUID = 7455805264680210747L;
+
+    private String name;
+
+    private ReferenceMod userOwner;
+
+    private ReferenceMod roleOwner;
+
+    private Boolean inheritOwner;
+
+    private Boolean inheritTemplates;
+
+    private Boolean inheritPlainAttrs;
+
+    private Boolean inheritDerAttrs;
+
+    private Boolean inheritVirAttrs;
+
+    private Boolean inheritAccountPolicy;
+
+    private Boolean inheritPasswordPolicy;
+
+    private boolean modEntitlements;
+
+    private final List<String> entitlements = new ArrayList<>();
+
+    private boolean modRAttrTemplates;
+
+    private final List<String> rPlainAttrTemplates = new ArrayList<>();
+
+    private boolean modRDerAttrTemplates;
+
+    private final List<String> rDerAttrTemplates = new ArrayList<>();
+
+    private boolean modRVirAttrTemplates;
+
+    private final List<String> rVirAttrTemplates = new ArrayList<>();
+
+    private boolean modMAttrTemplates;
+
+    private final List<String> mPlainAttrTemplates = new ArrayList<>();
+
+    private boolean modMDerAttrTemplates;
+
+    private final List<String> mDerAttrTemplates = new ArrayList<>();
+
+    private boolean modMVirAttrTemplates;
+
+    private final List<String> mVirAttrTemplates = new ArrayList<>();
+
+    private ReferenceMod passwordPolicy;
+
+    private ReferenceMod accountPolicy;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public ReferenceMod getUserOwner() {
+        return userOwner;
+    }
+
+    public void setUserOwner(final ReferenceMod userOwner) {
+        this.userOwner = userOwner;
+    }
+
+    public ReferenceMod getRoleOwner() {
+        return roleOwner;
+    }
+
+    public void setRoleOwner(final ReferenceMod roleOwner) {
+        this.roleOwner = roleOwner;
+    }
+
+    public Boolean getInheritOwner() {
+        return inheritOwner;
+    }
+
+    public void setInheritOwner(final Boolean inheritOwner) {
+        this.inheritOwner = inheritOwner;
+    }
+
+    public Boolean getInheritTemplates() {
+        return inheritTemplates;
+    }
+
+    public void setInheritTemplates(final Boolean inheritTemplates) {
+        this.inheritTemplates = inheritTemplates;
+    }
+
+    public Boolean getInheritPlainAttrs() {
+        return inheritPlainAttrs;
+    }
+
+    public void setInheritPlainAttrs(final Boolean inheritAttrs) {
+        this.inheritPlainAttrs = inheritAttrs;
+    }
+
+    public Boolean getInheritDerAttrs() {
+        return inheritDerAttrs;
+    }
+
+    public void setInheritDerAttrs(final Boolean inheritDerAttrs) {
+        this.inheritDerAttrs = inheritDerAttrs;
+    }
+
+    public Boolean getInheritVirAttrs() {
+        return inheritVirAttrs;
+    }
+
+    public void setInheritVirAttrs(final Boolean inheritVirAttrs) {
+        this.inheritVirAttrs = inheritVirAttrs;
+    }
+
+    public boolean isModEntitlements() {
+        return modEntitlements;
+    }
+
+    public void setModEntitlements(final boolean modEntitlements) {
+        this.modEntitlements = modEntitlements;
+    }
+
+    @XmlElementWrapper(name = "entitlements")
+    @XmlElement(name = "entitlement")
+    @JsonProperty("entitlements")
+    public List<String> getEntitlements() {
+        return entitlements;
+    }
+
+    public boolean isModRAttrTemplates() {
+        return modRAttrTemplates;
+    }
+
+    public void setModRAttrTemplates(final boolean modRAttrTemplates) {
+        this.modRAttrTemplates = modRAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "rPlainAttrTemplates")
+    @XmlElement(name = "rAttrTemplate")
+    @JsonProperty("rPlainAttrTemplates")
+    public List<String> getRPlainAttrTemplates() {
+        return rPlainAttrTemplates;
+    }
+
+    public boolean isModRDerAttrTemplates() {
+        return modRDerAttrTemplates;
+    }
+
+    public void setModRDerAttrTemplates(final boolean modRDerAttrTemplates) {
+        this.modRDerAttrTemplates = modRDerAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "rDerAttrTemplates")
+    @XmlElement(name = "rDerAttrTemplate")
+    @JsonProperty("rDerAttrTemplates")
+    public List<String> getRDerAttrTemplates() {
+        return rDerAttrTemplates;
+    }
+
+    public boolean isModRVirAttrTemplates() {
+        return modRVirAttrTemplates;
+    }
+
+    public void setModRVirAttrTemplates(final boolean modRVirAttrTemplates) {
+        this.modRVirAttrTemplates = modRVirAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "rVirAttrTemplates")
+    @XmlElement(name = "rVirAttrTemplate")
+    @JsonProperty("rVirAttrTemplates")
+    public List<String> getRVirAttrTemplates() {
+        return rVirAttrTemplates;
+    }
+
+    public boolean isModMAttrTemplates() {
+        return modMAttrTemplates;
+    }
+
+    public void setModMAttrTemplates(final boolean modMAttrTemplates) {
+        this.modMAttrTemplates = modMAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "mPlainAttrTemplates")
+    @XmlElement(name = "mAttrTemplate")
+    @JsonProperty("mPlainAttrTemplates")
+    public List<String> getMPlainAttrTemplates() {
+        return mPlainAttrTemplates;
+    }
+
+    public boolean isModMDerAttrTemplates() {
+        return modMDerAttrTemplates;
+    }
+
+    public void setModMDerAttrTemplates(final boolean modMDerAttrTemplates) {
+        this.modMDerAttrTemplates = modMDerAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "mDerAttrTemplates")
+    @XmlElement(name = "mDerAttrTemplate")
+    @JsonProperty("mDerAttrTemplates")
+    public List<String> getMDerAttrTemplates() {
+        return mDerAttrTemplates;
+    }
+
+    public boolean isModMVirAttrTemplates() {
+        return modMVirAttrTemplates;
+    }
+
+    public void setModMVirAttrTemplates(final boolean modMVirAttrTemplates) {
+        this.modMVirAttrTemplates = modMVirAttrTemplates;
+    }
+
+    @XmlElementWrapper(name = "mVirAttrTemplates")
+    @XmlElement(name = "mVirAttrTemplate")
+    @JsonProperty("mVirAttrTemplates")
+    public List<String> getMVirAttrTemplates() {
+        return mVirAttrTemplates;
+    }
+
+    public ReferenceMod getPasswordPolicy() {
+        return passwordPolicy;
+    }
+
+    public void setPasswordPolicy(final ReferenceMod passwordPolicy) {
+        this.passwordPolicy = passwordPolicy;
+    }
+
+    public Boolean getInheritPasswordPolicy() {
+        return inheritPasswordPolicy;
+    }
+
+    public void setInheritPasswordPolicy(final Boolean inheritPasswordPolicy) {
+        this.inheritPasswordPolicy = inheritPasswordPolicy;
+    }
+
+    public ReferenceMod getAccountPolicy() {
+        return accountPolicy;
+    }
+
+    public void setAccountPolicy(final ReferenceMod accountPolicy) {
+        this.accountPolicy = accountPolicy;
+    }
+
+    public Boolean getInheritAccountPolicy() {
+        return inheritAccountPolicy;
+    }
+
+    public void setInheritAccountPolicy(final Boolean inheritAccountPolicy) {
+        this.inheritAccountPolicy = inheritAccountPolicy;
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty() && name == null && userOwner == null && roleOwner == null
+                && inheritTemplates == null && inheritOwner == null
+                && inheritAccountPolicy == null && inheritPasswordPolicy == null
+                && inheritPlainAttrs == null && inheritDerAttrs == null && inheritVirAttrs == null
+                && accountPolicy == null && passwordPolicy == null && entitlements.isEmpty()
+                && rPlainAttrTemplates.isEmpty() && rDerAttrTemplates.isEmpty() && rVirAttrTemplates.isEmpty()
+                && mPlainAttrTemplates.isEmpty() && mDerAttrTemplates.isEmpty() && mVirAttrTemplates.isEmpty();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
new file mode 100644
index 0000000..3ddf263
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
@@ -0,0 +1,108 @@
+/*
+ * 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.common.lib.mod;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "statusMod")
+@XmlType
+public class StatusMod extends AbstractBaseBean {
+
+    private static final long serialVersionUID = 3230910033784302656L;
+
+    @XmlEnum
+    @XmlType(name = "statusModType")
+    public enum ModType {
+
+        ACTIVATE,
+        SUSPEND,
+        REACTIVATE;
+
+    }
+
+    /**
+     * Id of user to for which status update is requested.
+     */
+    private long id;
+
+    private ModType type;
+
+    /**
+     * Update token (if required).
+     */
+    private String token;
+
+    /**
+     * Whether update should be performed on internal storage.
+     */
+    private boolean onSyncope = true;
+
+    /**
+     * External resources for which update is needed to be propagated.
+     */
+    private final List<String> resourceNames = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public ModType getType() {
+        return type;
+    }
+
+    public void setType(final ModType type) {
+        this.type = type;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(final String token) {
+        this.token = token;
+    }
+
+    public boolean isOnSyncope() {
+        return onSyncope;
+    }
+
+    public void setOnSyncope(final boolean onSyncope) {
+        this.onSyncope = onSyncope;
+    }
+
+    @XmlElementWrapper(name = "resources")
+    @XmlElement(name = "resource")
+    @JsonProperty("resources")
+    public List<String> getResourceNames() {
+        return resourceNames;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/UserMod.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/UserMod.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/UserMod.java
new file mode 100644
index 0000000..0f421be
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/UserMod.java
@@ -0,0 +1,123 @@
+/*
+ * 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.common.lib.mod;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.HashSet;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "userMod")
+@XmlType
+public class UserMod extends AbstractSubjectMod {
+
+    private static final long serialVersionUID = 3081848906558106204L;
+
+    private String password;
+
+    private String username;
+
+    private final Set<MembershipMod> membershipsToAdd;
+
+    private final Set<Long> membershipsToRemove;
+
+    private StatusMod pwdPropRequest;
+
+    private Long securityQuestion;
+
+    private String securityAnswer;
+
+    public UserMod() {
+        super();
+
+        membershipsToAdd = new HashSet<>();
+        membershipsToRemove = new HashSet<>();
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(final String password) {
+        this.password = password;
+    }
+
+    @XmlElementWrapper(name = "membershipsToAdd")
+    @XmlElement(name = "membership")
+    @JsonProperty("membershipsToAdd")
+    public Set<MembershipMod> getMembershipsToAdd() {
+        return membershipsToAdd;
+    }
+
+    @XmlElementWrapper(name = "membershipsToRemove")
+    @XmlElement(name = "membership")
+    @JsonProperty("membershipsToRemove")
+    public Set<Long> getMembershipsToRemove() {
+        return membershipsToRemove;
+    }
+
+    public StatusMod getPwdPropRequest() {
+        return pwdPropRequest;
+    }
+
+    public void setPwdPropRequest(final StatusMod pwdPropRequest) {
+        this.pwdPropRequest = pwdPropRequest;
+    }
+
+    public Long getSecurityQuestion() {
+        return securityQuestion;
+    }
+
+    public void setSecurityQuestion(final Long securityQuestion) {
+        this.securityQuestion = securityQuestion;
+    }
+
+    public String getSecurityAnswer() {
+        return securityAnswer;
+    }
+
+    public void setSecurityAnswer(final String securityAnswer) {
+        this.securityAnswer = securityAnswer;
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty()
+                && password == null
+                && username == null
+                && membershipsToAdd.isEmpty()
+                && membershipsToRemove.isEmpty()
+                && pwdPropRequest == null
+                && securityQuestion == null
+                && securityAnswer == null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/package-info.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/package-info.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/package-info.java
new file mode 100644
index 0000000..866b275
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/mod/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NAMESPACE)
+package org.apache.syncope.common.lib.mod;
+
+import javax.xml.bind.annotation.XmlSchema;
+import org.apache.syncope.common.lib.SyncopeConstants;

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/RoleReportletConf.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/RoleReportletConf.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/RoleReportletConf.java
index 55e2da9..295316b 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/RoleReportletConf.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/RoleReportletConf.java
@@ -39,7 +39,7 @@ public class RoleReportletConf extends AbstractReportletConf {
     @XmlType(name = "roleReportletConfFeature")
     public enum Feature {
 
-        id,
+        key,
         name,
         roleOwner,
         userOwner,

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/UserReportletConf.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/UserReportletConf.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/UserReportletConf.java
index 2c6ed17..afd806a 100644
--- a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/UserReportletConf.java
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/report/UserReportletConf.java
@@ -38,7 +38,7 @@ public class UserReportletConf extends AbstractReportletConf {
     @XmlType(name = "userReportletConfFeature")
     public enum Feature {
 
-        id,
+        key,
         username,
         workflowId,
         status,

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/OrderByClauseBuilder.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/OrderByClauseBuilder.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/OrderByClauseBuilder.java
new file mode 100644
index 0000000..41d152c
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/OrderByClauseBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.common.lib.search;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Simple builder for generating <tt>orderby</tt> values.
+ */
+public class OrderByClauseBuilder {
+
+    private final StringBuilder builder = new StringBuilder();
+
+    public OrderByClauseBuilder asc(final String key) {
+        builder.append(key).append(" ASC,");
+        return this;
+    }
+
+    public OrderByClauseBuilder desc(final String key) {
+        builder.append(key).append(" DESC,");
+        return this;
+    }
+
+    public String build() {
+        return builder.length() == 0
+                ? StringUtils.EMPTY
+                : builder.deleteCharAt(builder.length() - 1).toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleFiqlSearchConditionBuilder.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleFiqlSearchConditionBuilder.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleFiqlSearchConditionBuilder.java
new file mode 100644
index 0000000..b6cbb6d
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleFiqlSearchConditionBuilder.java
@@ -0,0 +1,90 @@
+/*
+ * 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.common.lib.search;
+
+import java.util.Map;
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+
+/**
+ * Extends <tt>SyncopeFiqlSearchConditionBuilder</tt> by providing some additional facilities for searching
+ * roles in Syncope.
+ */
+public class RoleFiqlSearchConditionBuilder extends SyncopeFiqlSearchConditionBuilder {
+
+    public RoleFiqlSearchConditionBuilder() {
+        super();
+    }
+
+    public RoleFiqlSearchConditionBuilder(final Map<String, String> properties) {
+        super(properties);
+    }
+
+    @Override
+    protected Builder newBuilderInstance() {
+        return new Builder(properties);
+    }
+
+    @Override
+    public RoleProperty is(final String property) {
+        return newBuilderInstance().is(property);
+    }
+
+    public CompleteCondition hasEntitlements(final String entitlement, final String... moreEntitlements) {
+        return newBuilderInstance().is(SpecialAttr.ENTITLEMENTS.toString()).
+                hasEntitlements(entitlement, moreEntitlements);
+    }
+
+    public CompleteCondition hasNotEntitlements(final String entitlement, final String... moreEntitlements) {
+        return newBuilderInstance().is(SpecialAttr.ENTITLEMENTS.toString()).
+                hasNotEntitlements(entitlement, moreEntitlements);
+    }
+
+    protected static class Builder extends SyncopeFiqlSearchConditionBuilder.Builder
+            implements RoleProperty, CompleteCondition {
+
+        public Builder(final Map<String, String> properties) {
+            super(properties);
+        }
+
+        public Builder(final Builder parent) {
+            super(parent);
+        }
+
+        @Override
+        public RoleProperty is(final String property) {
+            Builder b = new Builder(this);
+            b.result = property;
+            return b;
+        }
+
+        @Override
+        public CompleteCondition hasEntitlements(final String entitlement, final String... moreEntitlements) {
+            this.result = SpecialAttr.ENTITLEMENTS.toString();
+            return condition(FiqlParser.EQ, entitlement, (Object[]) moreEntitlements);
+        }
+
+        @Override
+        public CompleteCondition hasNotEntitlements(final String entitlement, final String... moreEntitlements) {
+            this.result = SpecialAttr.ENTITLEMENTS.toString();
+            return condition(FiqlParser.NEQ, entitlement, (Object[]) moreEntitlements);
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleProperty.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleProperty.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleProperty.java
new file mode 100644
index 0000000..433ed11
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/search/RoleProperty.java
@@ -0,0 +1,29 @@
+/*
+ * 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.common.lib.search;
+
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+
+public interface RoleProperty extends SyncopeProperty {
+
+    CompleteCondition hasEntitlements(String entitlement, String... moreEntitlements);
+
+    CompleteCondition hasNotEntitlements(String entitlement, String... moreEntitlements);
+
+}


[09/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
new file mode 100644
index 0000000..fbfb127
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/PolicyController.java
@@ -0,0 +1,197 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.AbstractPolicyTO;
+import org.apache.syncope.common.lib.to.AccountPolicyTO;
+import org.apache.syncope.common.lib.to.PasswordPolicyTO;
+import org.apache.syncope.common.lib.to.SyncPolicyTO;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.server.logic.data.PolicyDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PolicyController extends AbstractTransactionalLogic<AbstractPolicyTO> {
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    @Autowired
+    private PolicyDataBinder binder;
+
+    @PreAuthorize("hasRole('POLICY_CREATE')")
+    public <T extends AbstractPolicyTO> T create(final T policyTO) {
+        return binder.getPolicyTO(policyDAO.save(binder.getPolicy(null, policyTO)));
+    }
+
+    private <T extends AbstractPolicyTO, K extends Policy> T update(final T policyTO, final K policy) {
+        binder.getPolicy(policy, policyTO);
+        K savedPolicy = policyDAO.save(policy);
+        return binder.getPolicyTO(savedPolicy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public PasswordPolicyTO update(final PasswordPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof PasswordPolicy)) {
+            throw new NotFoundException("PasswordPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public AccountPolicyTO update(final AccountPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof AccountPolicy)) {
+            throw new NotFoundException("AccountPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_UPDATE')")
+    public SyncPolicyTO update(final SyncPolicyTO policyTO) {
+        Policy policy = policyDAO.find(policyTO.getId());
+        if (!(policy instanceof SyncPolicy)) {
+            throw new NotFoundException("SyncPolicy with id " + policyTO.getId());
+        }
+
+        return update(policyTO, policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_LIST')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractPolicyTO> List<T> list(final PolicyType type) {
+
+        List<? extends Policy> policies = policyDAO.find(type);
+
+        final List<T> policyTOs = new ArrayList<T>();
+        for (Policy policy : policies) {
+            policyTOs.add((T) binder.getPolicyTO(policy));
+        }
+
+        return policyTOs;
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public PasswordPolicyTO getGlobalPasswordPolicy() {
+        PasswordPolicy policy = policyDAO.getGlobalPasswordPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No password policy found");
+        }
+
+        return (PasswordPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public AccountPolicyTO getGlobalAccountPolicy() {
+        AccountPolicy policy = policyDAO.getGlobalAccountPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No account policy found");
+        }
+
+        return (AccountPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public SyncPolicyTO getGlobalSyncPolicy() {
+        SyncPolicy policy = policyDAO.getGlobalSyncPolicy();
+        if (policy == null) {
+            throw new NotFoundException("No sync policy found");
+        }
+
+        return (SyncPolicyTO) binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_READ')")
+    public <T extends AbstractPolicyTO> T read(final Long id) {
+        Policy policy = policyDAO.find(id);
+        if (policy == null) {
+            throw new NotFoundException("Policy " + id + " not found");
+        }
+
+        return binder.getPolicyTO(policy);
+    }
+
+    @PreAuthorize("hasRole('POLICY_DELETE')")
+    public <T extends AbstractPolicyTO> T delete(final Long id) {
+        Policy policy = policyDAO.find(id);
+        if (policy == null) {
+            throw new NotFoundException("Policy " + id + " not found");
+        }
+
+        T policyToDelete = binder.getPolicyTO(policy);
+        policyDAO.delete(policy);
+
+        return policyToDelete;
+    }
+
+    @PreAuthorize("hasRole('POLICY_LIST')")
+    public Set<String> getSyncCorrelationRuleClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.SYNC_CORRELATION_RULES);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractPolicyTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof AbstractPolicyTO) {
+                    id = ((AbstractPolicyTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getPolicyTO(policyDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
new file mode 100644
index 0000000..0d8af57
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ReportLogic.java
@@ -0,0 +1,348 @@
+/*
+ * 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.server.logic;
+
+import java.io.ByteArrayInputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipInputStream;
+import org.apache.cocoon.optional.pipeline.components.sax.fop.FopSerializer;
+import org.apache.cocoon.pipeline.NonCachingPipeline;
+import org.apache.cocoon.pipeline.Pipeline;
+import org.apache.cocoon.sax.SAXPipelineComponent;
+import org.apache.cocoon.sax.component.XMLGenerator;
+import org.apache.cocoon.sax.component.XMLSerializer;
+import org.apache.cocoon.sax.component.XSLTTransformer;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.to.ReportExecTO;
+import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ReportExecExportFormat;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.ReportDAO;
+import org.apache.syncope.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.ReportExec;
+import org.apache.syncope.server.logic.data.ReportDataBinder;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.logic.report.Reportlet;
+import org.apache.syncope.server.logic.report.TextSerializer;
+import org.apache.xmlgraphics.util.MimeConstants;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ReportLogic extends AbstractTransactionalLogic<ReportTO> {
+
+    @Autowired
+    private ReportDAO reportDAO;
+
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    @Autowired
+    private JobInstanceLoader jobInstanceLoader;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private ReportDataBinder binder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @PreAuthorize("hasRole('REPORT_CREATE')")
+    public ReportTO create(final ReportTO reportTO) {
+        Report report = entityFactory.newEntity(Report.class);
+        binder.getReport(report, reportTO);
+        report = reportDAO.save(report);
+
+        try {
+            jobInstanceLoader.registerJob(report);
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for report " + report.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_UPDATE')")
+    public ReportTO update(final ReportTO reportTO) {
+        Report report = reportDAO.find(reportTO.getId());
+        if (report == null) {
+            throw new NotFoundException("Report " + reportTO.getId());
+        }
+
+        binder.getReport(report, reportTO);
+        report = reportDAO.save(report);
+
+        try {
+            jobInstanceLoader.registerJob(report);
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for report " + report.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    public int count() {
+        return reportDAO.count();
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    public List<ReportTO> list(final int page, final int size, final List<OrderByClause> orderByClauses) {
+        List<Report> reports = reportDAO.findAll(page, size, orderByClauses);
+        List<ReportTO> result = new ArrayList<ReportTO>(reports.size());
+        for (Report report : reports) {
+            result.add(binder.getReportTO(report));
+        }
+        return result;
+    }
+
+    @PreAuthorize("hasRole('REPORT_LIST')")
+    @SuppressWarnings("rawtypes")
+    public Set<String> getReportletConfClasses() {
+        Set<String> reportletConfClasses = new HashSet<String>();
+
+        for (Class<Reportlet> reportletClass : binder.getAllReportletClasses()) {
+            Class<? extends ReportletConf> reportletConfClass = binder.getReportletConfClass(reportletClass);
+            if (reportletConfClass != null) {
+                reportletConfClasses.add(reportletConfClass.getName());
+            }
+        }
+
+        return reportletConfClasses;
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public ReportTO read(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+        return binder.getReportTO(report);
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    @Transactional(readOnly = true)
+    public ReportExecTO readExecution(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+        return binder.getReportExecTO(reportExec);
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public void exportExecutionResult(final OutputStream os, final ReportExec reportExec,
+            final ReportExecExportFormat format) {
+
+        // streaming SAX handler from a compressed byte array stream
+        ByteArrayInputStream bais = new ByteArrayInputStream(reportExec.getExecResult());
+        ZipInputStream zis = new ZipInputStream(bais);
+        try {
+            // a single ZipEntry in the ZipInputStream (see ReportJob)
+            zis.getNextEntry();
+
+            Pipeline<SAXPipelineComponent> pipeline = new NonCachingPipeline<SAXPipelineComponent>();
+            pipeline.addComponent(new XMLGenerator(zis));
+
+            Map<String, Object> parameters = new HashMap<String, Object>();
+            parameters.put("status", reportExec.getStatus());
+            parameters.put("message", reportExec.getMessage());
+            parameters.put("startDate", reportExec.getStartDate());
+            parameters.put("endDate", reportExec.getEndDate());
+
+            switch (format) {
+                case HTML:
+                    XSLTTransformer xsl2html = new XSLTTransformer(getClass().getResource("/report/report2html.xsl"));
+                    xsl2html.setParameters(parameters);
+                    pipeline.addComponent(xsl2html);
+                    pipeline.addComponent(XMLSerializer.createXHTMLSerializer());
+                    break;
+
+                case PDF:
+                    XSLTTransformer xsl2pdf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
+                    xsl2pdf.setParameters(parameters);
+                    pipeline.addComponent(xsl2pdf);
+                    pipeline.addComponent(new FopSerializer(MimeConstants.MIME_PDF));
+                    break;
+
+                case RTF:
+                    XSLTTransformer xsl2rtf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
+                    xsl2rtf.setParameters(parameters);
+                    pipeline.addComponent(xsl2rtf);
+                    pipeline.addComponent(new FopSerializer(MimeConstants.MIME_RTF));
+                    break;
+
+                case CSV:
+                    XSLTTransformer xsl2csv = new XSLTTransformer(getClass().getResource("/report/report2csv.xsl"));
+                    xsl2csv.setParameters(parameters);
+                    pipeline.addComponent(xsl2csv);
+                    pipeline.addComponent(new TextSerializer());
+                    break;
+
+                case XML:
+                default:
+                    pipeline.addComponent(XMLSerializer.createXMLSerializer());
+            }
+
+            pipeline.setup(os);
+            pipeline.execute();
+
+            LOG.debug("Result of {} successfully exported as {}", reportExec, format);
+        } catch (Exception e) {
+            LOG.error("While exporting content", e);
+        } finally {
+            IOUtils.closeQuietly(zis);
+            IOUtils.closeQuietly(bais);
+        }
+    }
+
+    @PreAuthorize("hasRole('REPORT_READ')")
+    public ReportExec getAndCheckReportExec(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+        if (!ReportExecStatus.SUCCESS.name().equals(reportExec.getStatus()) || reportExec.getExecResult() == null) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidReportExec);
+            sce.getElements().add(reportExec.getExecResult() == null
+                    ? "No report data produced"
+                    : "Report did not run successfully");
+            throw sce;
+        }
+        return reportExec;
+    }
+
+    @PreAuthorize("hasRole('REPORT_EXECUTE')")
+    public ReportExecTO execute(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+
+        try {
+            jobInstanceLoader.registerJob(report);
+
+            scheduler.getScheduler().triggerJob(
+                    new JobKey(JobInstanceLoader.getJobName(report), Scheduler.DEFAULT_GROUP));
+        } catch (Exception e) {
+            LOG.error("While executing report {}", report, e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        ReportExecTO result = new ReportExecTO();
+        result.setReport(reportId);
+        result.setStartDate(new Date());
+        result.setStatus(ReportExecStatus.STARTED.name());
+        result.setMessage("Job fired; waiting for results...");
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('REPORT_DELETE')")
+    public ReportTO delete(final Long reportId) {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        }
+
+        ReportTO deletedReport = binder.getReportTO(report);
+        jobInstanceLoader.unregisterJob(report);
+        reportDAO.delete(report);
+        return deletedReport;
+    }
+
+    @PreAuthorize("hasRole('REPORT_DELETE')")
+    public ReportExecTO deleteExecution(final Long executionId) {
+        ReportExec reportExec = reportExecDAO.find(executionId);
+        if (reportExec == null) {
+            throw new NotFoundException("Report execution " + executionId);
+        }
+
+        ReportExecTO reportExecToDelete = binder.getReportExecTO(reportExec);
+        reportExecDAO.delete(reportExec);
+        return reportExecToDelete;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ReportTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args) && ("create".equals(method.getName())
+                || "update".equals(method.getName())
+                || "delete".equals(method.getName()))) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof ReportTO) {
+                    id = ((ReportTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getReportTO(reportDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
new file mode 100644
index 0000000..d1b2851
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ResourceLogic.java
@@ -0,0 +1,301 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.dao.ConnInstanceDAO;
+import org.apache.syncope.persistence.api.dao.DuplicateException;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.provisioning.api.Connector;
+import org.apache.syncope.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.logic.data.ResourceDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.utils.ConnObjectUtil;
+import org.apache.syncope.server.utils.MappingUtil;
+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.Uid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private ConnInstanceDAO connInstanceDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private ResourceDataBinder binder;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private ConnectorFactory connFactory;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    @PreAuthorize("hasRole('RESOURCE_CREATE')")
+    public ResourceTO create(final ResourceTO resourceTO) {
+        if (StringUtils.isBlank(resourceTO.getKey())) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+            sce.getElements().add("Resource name");
+            throw sce;
+        }
+
+        if (resourceDAO.find(resourceTO.getKey()) != null) {
+            throw new DuplicateException("Resource '" + resourceTO.getKey() + "'");
+        }
+
+        ExternalResource resource = null;
+        try {
+            resource = resourceDAO.save(binder.create(resourceTO));
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_UPDATE')")
+    public ResourceTO update(final ResourceTO resourceTO) {
+        ExternalResource resource = resourceDAO.find(resourceTO.getKey());
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceTO.getKey() + "'");
+        }
+
+        resource = binder.update(resource, resourceTO);
+        try {
+            resource = resourceDAO.save(resource);
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_DELETE')")
+    public ResourceTO delete(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        ResourceTO resourceToDelete = binder.getResourceTO(resource);
+
+        resourceDAO.delete(resourceName);
+
+        return resourceToDelete;
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_READ')")
+    @Transactional(readOnly = true)
+    public ResourceTO read(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        return binder.getResourceTO(resource);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_READ')")
+    public Set<String> getPropagationActionsClasses() {
+        Set<String> actionsClasses = classNamesLoader.getClassNames(
+                ImplementationClassNamesLoader.Type.PROPAGATION_ACTIONS);
+
+        return actionsClasses;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public List<ResourceTO> list(final Long connInstanceId) {
+        List<? extends ExternalResource> resources;
+
+        if (connInstanceId == null) {
+            resources = resourceDAO.findAll();
+        } else {
+            ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+            resources = connInstance.getResources();
+        }
+
+        return binder.getResourceTOs(resources);
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_GETCONNECTOROBJECT')")
+    @Transactional(readOnly = true)
+    public ConnObjectTO getConnectorObject(final String resourceName, final SubjectType type, final Long id) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+
+        Subject<?, ?, ?> subject = type == SubjectType.USER
+                ? userDAO.find(id)
+                : roleDAO.find(id);
+        if (subject == null) {
+            throw new NotFoundException(type + " " + id);
+        }
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
+
+        MappingItem accountIdItem = attrUtil.getAccountIdItem(resource);
+        if (accountIdItem == null) {
+            throw new NotFoundException("AccountId mapping for " + type + " " + id + " on resource '" + resourceName
+                    + "'");
+        }
+        final String accountIdValue = MappingUtil.getAccountIdValue(
+                subject, resource, attrUtil.getAccountIdItem(resource));
+
+        final ObjectClass objectClass = SubjectType.USER == type ? ObjectClass.ACCOUNT : ObjectClass.GROUP;
+
+        final Connector connector = connFactory.getConnector(resource);
+        final ConnectorObject connectorObject = connector.getObject(objectClass, new Uid(accountIdValue),
+                connector.getOperationOptions(attrUtil.getMappingItems(resource, MappingPurpose.BOTH)));
+        if (connectorObject == null) {
+            throw new NotFoundException("Object " + accountIdValue + " with class " + objectClass
+                    + "not found on resource " + resourceName);
+        }
+
+        final Set<Attribute> attributes = connectorObject.getAttributes();
+        if (AttributeUtil.find(Uid.NAME, attributes) == null) {
+            attributes.add(connectorObject.getUid());
+        }
+        if (AttributeUtil.find(Name.NAME, attributes) == null) {
+            attributes.add(connectorObject.getName());
+        }
+
+        return connObjectUtil.getConnObjectTO(connectorObject);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public boolean check(final ResourceTO resourceTO) {
+        final ConnInstance connInstance = binder.getConnInstance(resourceTO);
+
+        final Connector connector = connFactory.createConnector(connInstance, connInstance.getConfiguration());
+
+        boolean result;
+        try {
+            connector.test();
+            result = true;
+        } catch (Exception e) {
+            LOG.error("Test connection failure {}", e);
+            result = false;
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('RESOURCE_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        if (bulkAction.getOperation() == BulkAction.Type.DELETE) {
+            for (String name : bulkAction.getTargets()) {
+                try {
+                    res.add(delete(name).getKey(), BulkActionResult.Status.SUCCESS);
+                } catch (Exception e) {
+                    LOG.error("Error performing delete for resource {}", name, e);
+                    res.add(name, BulkActionResult.Status.FAILURE);
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ResourceTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String name = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; name == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    name = (String) args[i];
+                } else if (args[i] instanceof ResourceTO) {
+                    name = ((ResourceTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (name != null) {
+            try {
+                return binder.getResourceTO(resourceDAO.find(name));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
new file mode 100644
index 0000000..aee0d27
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/RoleLogic.java
@@ -0,0 +1,405 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.AttributableTransformer;
+import org.apache.syncope.provisioning.api.RoleProvisioningManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.RoleDataBinder;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+/**
+ * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
+ * Spring's Transactional logic at class level.
+ */
+@Component
+public class RoleLogic extends AbstractSubjectLogic<RoleTO, RoleMod> {
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected SubjectSearchDAO searchDAO;
+
+    @Autowired
+    protected RoleDataBinder binder;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected AttributableTransformer attrTransformer;
+
+    @Resource(name = "anonymousUser")
+    protected String anonymousUser;
+
+    @Autowired
+    protected RoleProvisioningManager provisioningManager;
+
+    @PreAuthorize("hasAnyRole('ROLE_READ', T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional(readOnly = true)
+    @Override
+    public RoleTO read(final Long roleKey) {
+        Role role;
+        // bypass role entitlements check
+        if (anonymousUser.equals(AuthContextUtil.getAuthenticatedUsername())) {
+            role = roleDAO.find(roleKey);
+        } else {
+            role = roleDAO.authFetchRole(roleKey);
+        }
+
+        if (role == null) {
+            throw new NotFoundException("Role " + roleKey);
+        }
+
+        return binder.getRoleTO(role);
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    @Transactional(readOnly = true)
+    public RoleTO readSelf(final Long roleKey) {
+        // Explicit search instead of using binder.getRoleFromId() in order to bypass auth checks - will do here
+        Role role = roleDAO.find(roleKey);
+        if (role == null) {
+            throw new NotFoundException("Role " + roleKey);
+        }
+
+        Set<Long> ownedRoleIds;
+        User authUser = userDAO.find(AuthContextUtil.getAuthenticatedUsername());
+        if (authUser == null) {
+            ownedRoleIds = Collections.<Long>emptySet();
+        } else {
+            ownedRoleIds = authUser.getRoleIds();
+        }
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        allowedRoleIds.addAll(ownedRoleIds);
+        if (!allowedRoleIds.contains(role.getKey())) {
+            throw new UnauthorizedRoleException(role.getKey());
+        }
+
+        return binder.getRoleTO(role);
+    }
+
+    @PreAuthorize("hasRole('ROLE_READ')")
+    @Transactional(readOnly = true)
+    public RoleTO parent(final Long roleKey) {
+        Role role = roleDAO.authFetchRole(roleKey);
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        if (role.getParent() != null && !allowedRoleIds.contains(role.getParent().getKey())) {
+            throw new UnauthorizedRoleException(role.getParent().getKey());
+        }
+
+        RoleTO result = role.getParent() == null
+                ? null
+                : binder.getRoleTO(role.getParent());
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_READ')")
+    @Transactional(readOnly = true)
+    public List<RoleTO> children(final Long roleKey) {
+        Role role = roleDAO.authFetchRole(roleKey);
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+
+        List<Role> children = roleDAO.findChildren(role);
+        List<RoleTO> childrenTOs = new ArrayList<RoleTO>(children.size());
+        for (Role child : children) {
+            if (allowedRoleIds.contains(child.getKey())) {
+                childrenTOs.add(binder.getRoleTO(child));
+            }
+        }
+
+        return childrenTOs;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int count() {
+        return roleDAO.count();
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    @Override
+    public List<RoleTO> list(final int page, final int size, final List<OrderByClause> orderBy) {
+        List<Role> roles = roleDAO.findAll(page, size, orderBy);
+
+        List<RoleTO> roleTOs = new ArrayList<>(roles.size());
+        for (Role role : roles) {
+            roleTOs.add(binder.getRoleTO(role));
+        }
+
+        return roleTOs;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int searchCount(final SearchCond searchCondition) {
+        final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        return searchDAO.count(adminRoleIds, searchCondition, SubjectType.ROLE);
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public List<RoleTO> search(final SearchCond searchCondition, final int page, final int size,
+            final List<OrderByClause> orderBy) {
+
+        final List<Role> matchingRoles = searchDAO.search(
+                RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, page, size, orderBy, SubjectType.ROLE);
+
+        final List<RoleTO> result = new ArrayList<>(matchingRoles.size());
+        for (Role role : matchingRoles) {
+            result.add(binder.getRoleTO(role));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_CREATE')")
+    public RoleTO create(final RoleTO roleTO) {
+        // Check that this operation is allowed to be performed by caller
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        if (roleTO.getParent() != 0 && !allowedRoleIds.contains(roleTO.getParent())) {
+            throw new UnauthorizedRoleException(roleTO.getParent());
+        }
+
+        // Attributable transformation (if configured)
+        RoleTO actual = attrTransformer.transform(roleTO);
+        LOG.debug("Transformed: {}", actual);
+
+        /*
+         * Actual operations: workflow, propagation
+         */
+        Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(roleTO);
+        final RoleTO savedTO = binder.getRoleTO(created.getKey());
+        savedTO.getPropagationStatusTOs().addAll(created.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Override
+    public RoleTO update(final RoleMod roleMod) {
+        // Check that this operation is allowed to be performed by caller
+        roleDAO.authFetchRole(roleMod.getKey());
+
+        // Attribute value transformation (if configured)
+        RoleMod actual = attrTransformer.transform(roleMod);
+        LOG.debug("Transformed: {}", actual);
+
+        Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(roleMod);
+
+        final RoleTO updatedTO = binder.getRoleTO(updated.getKey());
+        updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return updatedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_DELETE')")
+    @Override
+    public RoleTO delete(final Long roleKey) {
+        List<Role> ownedRoles = roleDAO.findOwnedByRole(roleKey);
+        if (!ownedRoles.isEmpty()) {
+            List<String> owned = new ArrayList<String>(ownedRoles.size());
+            for (Role role : ownedRoles) {
+                owned.add(role.getKey() + " " + role.getName());
+            }
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RoleOwnership);
+            sce.getElements().addAll(owned);
+            throw sce;
+        }
+
+        List<PropagationStatus> statuses = provisioningManager.delete(roleKey);
+
+        RoleTO roleTO = new RoleTO();
+        roleTO.setKey(roleKey);
+
+        roleTO.getPropagationStatusTOs().addAll(statuses);
+
+        return roleTO;
+    }
+
+    @PreAuthorize("(hasRole('ROLE_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE)")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        if (bulkAction.getOperation() == BulkAction.Type.DELETE) {
+            for (String roleKey : bulkAction.getTargets()) {
+                try {
+                    res.add(delete(Long.valueOf(roleKey)).getKey(), BulkActionResult.Status.SUCCESS);
+                } catch (Exception e) {
+                    LOG.error("Error performing delete for role {}", roleKey, e);
+                    res.add(roleKey, BulkActionResult.Status.FAILURE);
+                }
+            }
+        } else {
+            LOG.warn("Unsupported bulk action: {}", bulkAction.getOperation());
+        }
+
+        return res;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO unlink(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToRemove().addAll(resources);
+        final Long updatedResult = provisioningManager.unlink(roleMod);
+
+        return binder.getRoleTO(updatedResult);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO link(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToAdd().addAll(resources);
+        return binder.getRoleTO(provisioningManager.link(roleMod));
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO unassign(final Long roleKey, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(roleKey);
+        roleMod.getResourcesToRemove().addAll(resources);
+        return update(roleMod);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO assign(
+            final Long roleKey, final Collection<String> resources, final boolean changePwd, final String password) {
+
+        final RoleMod userMod = new RoleMod();
+        userMod.setKey(roleKey);
+        userMod.getResourcesToAdd().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO deprovision(final Long roleKey, final Collection<String> resources) {
+        final Role role = roleDAO.authFetchRole(roleKey);
+
+        List<PropagationStatus> statuses = provisioningManager.deprovision(roleKey, resources);
+
+        final RoleTO updatedTO = binder.getRoleTO(role);
+        updatedTO.getPropagationStatusTOs().addAll(statuses);
+        return updatedTO;
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO provision(
+            final Long roleKey, final Collection<String> resources, final boolean changePwd, final String password) {
+        final RoleTO original = binder.getRoleTO(roleKey);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(roleKey, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
+    @Override
+    protected RoleTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof RoleTO) {
+                    id = ((RoleTO) args[i]).getKey();
+                } else if (args[i] instanceof RoleMod) {
+                    id = ((RoleMod) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getRoleTO(id);
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
new file mode 100644
index 0000000..675bd42
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SchemaLogic.java
@@ -0,0 +1,328 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.persistence.api.dao.DuplicateException;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.logic.data.SchemaDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SchemaLogic extends AbstractTransactionalLogic<AbstractSchemaTO> {
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private DerSchemaDAO derSchemaDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private SchemaDataBinder binder;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    private boolean doesSchemaExist(final SchemaType schemaType, final String name, final AttributableUtil attrUtil) {
+        boolean found;
+
+        switch (schemaType) {
+            case VIRTUAL:
+                found = virSchemaDAO.find(name, attrUtil.virSchemaClass()) != null;
+                break;
+
+            case DERIVED:
+                found = derSchemaDAO.find(name, attrUtil.derSchemaClass()) != null;
+                break;
+
+            case PLAIN:
+                found = plainSchemaDAO.find(name, attrUtil.plainSchemaClass()) != null;
+                break;
+
+            default:
+                found = false;
+        }
+
+        return found;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_CREATE')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> T create(
+            final AttributableType attrType, final SchemaType schemaType, final T schemaTO) {
+
+        if (StringUtils.isBlank(schemaTO.getKey())) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+            sce.getElements().add("Schema name");
+            throw sce;
+        }
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (doesSchemaExist(schemaType, schemaTO.getKey(), attrUtil)) {
+            throw new DuplicateException(schemaType + "/" + attrType + "/" + schemaTO.getKey());
+        }
+
+        T created;
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = attrUtil.newVirSchema();
+                binder.create((VirSchemaTO) schemaTO, virSchema);
+                virSchema = virSchemaDAO.save(virSchema);
+                created = (T) binder.getVirSchemaTO(virSchema);
+                break;
+            case DERIVED:
+                DerSchema derSchema = attrUtil.newDerSchema();
+                binder.create((DerSchemaTO) schemaTO, derSchema);
+                derSchema = derSchemaDAO.save(derSchema);
+
+                created = (T) binder.getDerSchemaTO(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema normalSchema = attrUtil.newPlainSchema();
+                binder.create((PlainSchemaTO) schemaTO, normalSchema);
+                normalSchema = plainSchemaDAO.save(normalSchema);
+
+                created = (T) binder.getSchemaTO(normalSchema, attrUtil);
+        }
+        return created;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_DELETE')")
+    public void delete(final AttributableType attrType, final SchemaType schemaType, final String schemaName) {
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (!doesSchemaExist(schemaType, schemaName, attrUtil)) {
+            throw new NotFoundException(schemaType + "/" + attrType + "/" + schemaName);
+        }
+
+        switch (schemaType) {
+            case VIRTUAL:
+                virSchemaDAO.delete(schemaName, attrUtil);
+                break;
+
+            case DERIVED:
+                derSchemaDAO.delete(schemaName, attrUtil);
+                break;
+
+            case PLAIN:
+            default:
+                plainSchemaDAO.delete(schemaName, attrUtil);
+        }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> List<T> list(final AttributableType attrType, final SchemaType schemaType) {
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        List<T> result;
+        switch (schemaType) {
+            case VIRTUAL:
+                List<VirSchema> virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
+                result = new ArrayList<>(virSchemas.size());
+                for (VirSchema derSchema : virSchemas) {
+                    result.add((T) binder.getVirSchemaTO(derSchema));
+                }
+                break;
+
+            case DERIVED:
+                List<DerSchema> derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
+                result = new ArrayList<>(derSchemas.size());
+                for (DerSchema derSchema : derSchemas) {
+                    result.add((T) binder.getDerSchemaTO(derSchema));
+                }
+                break;
+
+            case PLAIN:
+            default:
+                List<PlainSchema> schemas = plainSchemaDAO.findAll(attrUtil.plainSchemaClass());
+                result = new ArrayList<>(schemas.size());
+                for (PlainSchema schema : schemas) {
+                    result.add((T) binder.getSchemaTO(schema, attrUtil));
+                }
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_READ')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSchemaTO> T read(
+            final AttributableType attrType, final SchemaType schemaType, final String schemaName) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        T read;
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = virSchemaDAO.find(schemaName, attrUtil.virSchemaClass());
+                if (virSchema == null) {
+                    throw new NotFoundException("Virtual Schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getVirSchemaTO(virSchema);
+                break;
+
+            case DERIVED:
+                DerSchema derSchema = derSchemaDAO.find(schemaName, attrUtil.derSchemaClass());
+                if (derSchema == null) {
+                    throw new NotFoundException("Derived schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getDerSchemaTO(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema schema = plainSchemaDAO.find(schemaName, attrUtil.plainSchemaClass());
+                if (schema == null) {
+                    throw new NotFoundException("Schema '" + schemaName + "'");
+                }
+
+                read = (T) binder.getSchemaTO(schema, attrUtil);
+        }
+
+        return read;
+    }
+
+    @PreAuthorize("hasRole('SCHEMA_UPDATE')")
+    public <T extends AbstractSchemaTO> void update(
+            final AttributableType attrType, final SchemaType schemaType, final T schemaTO) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(attrType);
+
+        if (!doesSchemaExist(schemaType, schemaTO.getKey(), attrUtil)) {
+            throw new NotFoundException(schemaType + "/" + attrType + "/" + schemaTO.getKey());
+        }
+
+        switch (schemaType) {
+            case VIRTUAL:
+                VirSchema virSchema = virSchemaDAO.find(schemaTO.getKey(), attrUtil.virSchemaClass());
+                if (virSchema == null) {
+                    throw new NotFoundException("Virtual Schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((VirSchemaTO) schemaTO, virSchema);
+                virSchemaDAO.save(virSchema);
+                break;
+
+            case DERIVED:
+                DerSchema derSchema = derSchemaDAO.find(schemaTO.getKey(), attrUtil.derSchemaClass());
+                if (derSchema == null) {
+                    throw new NotFoundException("Derived schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((DerSchemaTO) schemaTO, derSchema);
+                derSchemaDAO.save(derSchema);
+                break;
+
+            case PLAIN:
+            default:
+                PlainSchema schema = plainSchemaDAO.find(schemaTO.getKey(), attrUtil.plainSchemaClass());
+                if (schema == null) {
+                    throw new NotFoundException("Schema '" + schemaTO.getKey() + "'");
+                }
+
+                binder.update((PlainSchemaTO) schemaTO, schema, attrUtil);
+                plainSchemaDAO.save(schema);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractSchemaTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String kind = null;
+        String name = null;
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; (name == null || kind == null) && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    if (kind == null) {
+                        kind = (String) args[i];
+                    } else {
+                        name = (String) args[i];
+                    }
+                } else if (args[i] instanceof AbstractSchemaTO) {
+                    name = ((AbstractSchemaTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (name != null) {
+            try {
+                final AttributableUtil attrUtil = attrUtilFactory.getInstance(kind);
+
+                AbstractSchemaTO result = null;
+
+                PlainSchema plainSchema = plainSchemaDAO.find(name, attrUtil.plainSchemaClass());
+                if (plainSchema == null) {
+                    DerSchema derSchema = derSchemaDAO.find(name, attrUtil.derSchemaClass());
+                    if (derSchema == null) {
+                        VirSchema virSchema = virSchemaDAO.find(name, attrUtil.virSchemaClass());
+                        if (virSchema != null) {
+                            result = binder.getVirSchemaTO(virSchema);
+                        }
+                    } else {
+                        result = binder.getDerSchemaTO(derSchema);
+                    }
+                } else {
+                    result = binder.getSchemaTO(plainSchema, attrUtil);
+                }
+
+                return result;
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
new file mode 100644
index 0000000..01b2a57
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/SecurityQuestionLogic.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.SecurityQuestionDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.server.logic.data.SecurityQuestionDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SecurityQuestionLogic extends AbstractTransactionalLogic<SecurityQuestionTO> {
+
+    @Autowired
+    private SecurityQuestionDAO securityQuestionDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private SecurityQuestionDataBinder binder;
+
+    @PreAuthorize("isAuthenticated()")
+    public List<SecurityQuestionTO> list() {
+        List<SecurityQuestionTO> result = new ArrayList<SecurityQuestionTO>();
+        for (SecurityQuestion securityQuestion : securityQuestionDAO.findAll()) {
+            result.add(binder.getSecurityQuestionTO(securityQuestion));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public SecurityQuestionTO read(final Long securityQuestionId) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionId);
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionId + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionId));
+        }
+
+        return binder.getSecurityQuestionTO(securityQuestion);
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_CREATE')")
+    public SecurityQuestionTO create(final SecurityQuestionTO securityQuestionTO) {
+        return binder.getSecurityQuestionTO(securityQuestionDAO.save(binder.create(securityQuestionTO)));
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_UPDATE')")
+    public SecurityQuestionTO update(final SecurityQuestionTO securityQuestionTO) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionTO.getKey());
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionTO.getKey() + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionTO.getKey()));
+        }
+
+        binder.update(securityQuestion, securityQuestionTO);
+        securityQuestion = securityQuestionDAO.save(securityQuestion);
+
+        return binder.getSecurityQuestionTO(securityQuestion);
+    }
+
+    @PreAuthorize("hasRole('SECURITY_QUESTION_DELETE')")
+    public SecurityQuestionTO delete(final Long securityQuestionId) {
+        SecurityQuestion securityQuestion = securityQuestionDAO.find(securityQuestionId);
+        if (securityQuestion == null) {
+            LOG.error("Could not find security question '" + securityQuestionId + "'");
+
+            throw new NotFoundException(String.valueOf(securityQuestionId));
+        }
+
+        SecurityQuestionTO deleted = binder.getSecurityQuestionTO(securityQuestion);
+        securityQuestionDAO.delete(securityQuestionId);
+        return deleted;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    public SecurityQuestionTO read(final String username) {
+        if (username == null) {
+            throw new NotFoundException("Null username");
+        }
+        User user = userDAO.find(username);
+        if (user == null) {
+            throw new NotFoundException("User " + username);
+        }
+
+        if (user.getSecurityQuestion() == null) {
+            LOG.error("Could not find security question for user '" + username + "'");
+
+            throw new NotFoundException("Security question for user " + username);
+        }
+
+        return binder.getSecurityQuestionTO(user.getSecurityQuestion());
+    }
+
+    @Override
+    protected SecurityQuestionTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof SecurityQuestionTO) {
+                    id = ((SecurityQuestionTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getSecurityQuestionTO(securityQuestionDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
new file mode 100644
index 0000000..11c9b4b
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/TaskLogic.java
@@ -0,0 +1,408 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.TaskExecTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.persistence.api.entity.task.TaskUtil;
+import org.apache.syncope.persistence.api.entity.task.TaskUtilFactory;
+import org.apache.syncope.provisioning.api.job.TaskJob;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.TaskDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.logic.notification.NotificationJob;
+import org.quartz.JobDataMap;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TaskLogic extends AbstractTransactionalLogic<AbstractTaskTO> {
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private TaskExecDAO taskExecDAO;
+
+    @Autowired
+    private TaskDataBinder binder;
+
+    @Autowired
+    private PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    private NotificationJob notificationJob;
+
+    @Autowired
+    private JobInstanceLoader jobInstanceLoader;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @Autowired
+    private TaskUtilFactory taskUtilFactory;
+
+    @PreAuthorize("hasRole('TASK_CREATE')")
+    public <T extends SchedTaskTO> T createSchedTask(final T taskTO) {
+        TaskUtil taskUtil = taskUtilFactory.getInstance(taskTO);
+
+        SchedTask task = binder.createSchedTask(taskTO, taskUtil);
+        task = taskDAO.save(task);
+
+        try {
+            jobInstanceLoader.registerJob(task, task.getJobClassName(), task.getCronExpression());
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for task " + task.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getTaskTO(task, taskUtil);
+    }
+
+    @PreAuthorize("hasRole('TASK_UPDATE')")
+    public SyncTaskTO updateSync(final SyncTaskTO taskTO) {
+        return updateSched(taskTO);
+    }
+
+    @PreAuthorize("hasRole('TASK_UPDATE')")
+    public <T extends SchedTaskTO> T updateSched(final SchedTaskTO taskTO) {
+        SchedTask task = taskDAO.find(taskTO.getId());
+        if (task == null) {
+            throw new NotFoundException("Task " + taskTO.getId());
+        }
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        binder.updateSchedTask(task, taskTO, taskUtil);
+        task = taskDAO.save(task);
+
+        try {
+            jobInstanceLoader.registerJob(task, task.getJobClassName(), task.getCronExpression());
+        } catch (Exception e) {
+            LOG.error("While registering quartz job for task " + task.getKey(), e);
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        return binder.getTaskTO(task, taskUtil);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public int count(final TaskType taskType) {
+        return taskDAO.count(taskType);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractTaskTO> List<T> list(final TaskType taskType,
+            final int page, final int size, final List<OrderByClause> orderByClauses) {
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(taskType);
+
+        List<Task> tasks = taskDAO.findAll(page, size, orderByClauses, taskType);
+        List<T> taskTOs = new ArrayList<>(tasks.size());
+        for (Task task : tasks) {
+            taskTOs.add((T) binder.getTaskTO(task, taskUtil));
+        }
+
+        return taskTOs;
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getJobClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.TASKJOB);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getSyncActionsClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.SYNC_ACTIONS);
+    }
+
+    @PreAuthorize("hasRole('TASK_LIST')")
+    public Set<String> getPushActionsClasses() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.PUSH_ACTIONS);
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public <T extends AbstractTaskTO> T read(final Long taskId) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        return binder.getTaskTO(task, taskUtilFactory.getInstance(task));
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public TaskExecTO readExecution(final Long executionId) {
+        TaskExec taskExec = taskExecDAO.find(executionId);
+        if (taskExec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+        return binder.getTaskExecTO(taskExec);
+    }
+
+    @PreAuthorize("hasRole('TASK_EXECUTE')")
+    public TaskExecTO execute(final Long taskId, final boolean dryRun) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        TaskExecTO result = null;
+        switch (taskUtil.getType()) {
+            case PROPAGATION:
+                final TaskExec propExec = taskExecutor.execute((PropagationTask) task);
+                result = binder.getTaskExecTO(propExec);
+                break;
+
+            case NOTIFICATION:
+                final TaskExec notExec = notificationJob.executeSingle((NotificationTask) task);
+                result = binder.getTaskExecTO(notExec);
+                break;
+
+            case SCHEDULED:
+            case SYNCHRONIZATION:
+            case PUSH:
+                try {
+                    jobInstanceLoader.registerJob(task,
+                            ((SchedTask) task).getJobClassName(),
+                            ((SchedTask) task).getCronExpression());
+
+                    JobDataMap map = new JobDataMap();
+                    map.put(TaskJob.DRY_RUN_JOBDETAIL_KEY, dryRun);
+
+                    scheduler.getScheduler().triggerJob(
+                            new JobKey(JobInstanceLoader.getJobName(task), Scheduler.DEFAULT_GROUP), map);
+                } catch (Exception e) {
+                    LOG.error("While executing task {}", task, e);
+
+                    SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
+                    sce.getElements().add(e.getMessage());
+                    throw sce;
+                }
+
+                result = new TaskExecTO();
+                result.setTask(taskId);
+                result.setStartDate(new Date());
+                result.setStatus("JOB_FIRED");
+                result.setMessage("Job fired; waiting for results...");
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('TASK_READ')")
+    public TaskExecTO report(final Long executionId, final PropagationTaskExecStatus status, final String message) {
+        TaskExec exec = taskExecDAO.find(executionId);
+        if (exec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+
+        SyncopeClientException sce = SyncopeClientException.build(
+                ClientExceptionType.InvalidPropagationTaskExecReport);
+
+        TaskUtil taskUtil = taskUtilFactory.getInstance(exec.getTask());
+        if (TaskType.PROPAGATION == taskUtil.getType()) {
+            PropagationTask task = (PropagationTask) exec.getTask();
+            if (task.getPropagationMode() != PropagationMode.TWO_PHASES) {
+                sce.getElements().add("Propagation mode: " + task.getPropagationMode());
+            }
+        } else {
+            sce.getElements().add("Task type: " + taskUtil);
+        }
+
+        switch (status) {
+            case SUCCESS:
+            case FAILURE:
+                break;
+
+            case CREATED:
+            case SUBMITTED:
+            case UNSUBMITTED:
+                sce.getElements().add("Execution status to be set: " + status);
+                break;
+
+            default:
+        }
+
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+
+        exec.setStatus(status.toString());
+        exec.setMessage(message);
+        return binder.getTaskExecTO(taskExecDAO.save(exec));
+    }
+
+    @PreAuthorize("hasRole('TASK_DELETE')")
+    public <T extends AbstractTaskTO> T delete(final Long taskId) {
+        Task task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        }
+        TaskUtil taskUtil = taskUtilFactory.getInstance(task);
+
+        T taskToDelete = binder.getTaskTO(task, taskUtil);
+
+        if (TaskType.SCHEDULED == taskUtil.getType()
+                || TaskType.SYNCHRONIZATION == taskUtil.getType()
+                || TaskType.PUSH == taskUtil.getType()) {
+            jobInstanceLoader.unregisterJob(task);
+        }
+
+        taskDAO.delete(task);
+        return taskToDelete;
+    }
+
+    @PreAuthorize("hasRole('TASK_DELETE')")
+    public TaskExecTO deleteExecution(final Long executionId) {
+        TaskExec taskExec = taskExecDAO.find(executionId);
+        if (taskExec == null) {
+            throw new NotFoundException("Task execution " + executionId);
+        }
+
+        TaskExecTO taskExecutionToDelete = binder.getTaskExecTO(taskExec);
+        taskExecDAO.delete(taskExec);
+        return taskExecutionToDelete;
+    }
+
+    @PreAuthorize("(hasRole('TASK_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE) or "
+            + "(hasRole('TASK_EXECUTE') and "
+            + "(#bulkAction.operation == #bulkAction.operation.EXECUTE or "
+            + "#bulkAction.operation == #bulkAction.operation.DRYRUN))")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        switch (bulkAction.getOperation()) {
+            case DELETE:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        res.add(delete(Long.valueOf(taskId)).getId(), BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing delete for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            case DRYRUN:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        execute(Long.valueOf(taskId), true);
+                        res.add(taskId, BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing dryrun for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            case EXECUTE:
+                for (String taskId : bulkAction.getTargets()) {
+                    try {
+                        execute(Long.valueOf(taskId), false);
+                        res.add(taskId, BulkActionResult.Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing execute for task {}", taskId, e);
+                        res.add(taskId, BulkActionResult.Status.FAILURE);
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected AbstractTaskTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)
+                && !"deleteExecution".equals(method.getName()) && !"readExecution".equals(method.getName())) {
+
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof AbstractTaskTO) {
+                    id = ((AbstractTaskTO) args[i]).getId();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                final Task task = taskDAO.find(id);
+                return binder.getTaskTO(task, taskUtilFactory.getInstance(task));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
new file mode 100644
index 0000000..aff28d9
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UnresolvedReferenceException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.logic;
+
+/**
+ * Indicates unresolved bean reference.
+ */
+public class UnresolvedReferenceException extends Exception {
+
+    private static final long serialVersionUID = -675489116009955632L;
+
+    public UnresolvedReferenceException() {
+        super();
+    }
+
+    public UnresolvedReferenceException(final Throwable cause) {
+        super(cause);
+    }
+}


[05/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/AbstractReportlet.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/AbstractReportlet.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/AbstractReportlet.java
new file mode 100644
index 0000000..8c8661b
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/AbstractReportlet.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.logic.report;
+
+import org.apache.syncope.common.lib.report.AbstractReportletConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Transactional;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+public abstract class AbstractReportlet<T extends AbstractReportletConf> implements Reportlet<T> {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractReportlet.class);
+
+    protected T conf;
+
+    public T getConf() {
+        return conf;
+    }
+
+    @Override
+    public void setConf(final T conf) {
+        this.conf = conf;
+    }
+
+    protected abstract void doExtract(ContentHandler handler) throws SAXException, ReportException;
+
+    @Override
+    @Transactional(readOnly = true)
+    public void extract(final ContentHandler handler) throws SAXException, ReportException {
+
+        if (conf == null) {
+            throw new ReportException(new IllegalArgumentException("No configuration provided"));
+        }
+
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, conf.getName());
+        atts.addAttribute("", "", ReportXMLConst.ATTR_CLASS, ReportXMLConst.XSD_STRING, getClass().getName());
+        handler.startElement("", "", ReportXMLConst.ELEMENT_REPORTLET, atts);
+
+        doExtract(handler);
+
+        handler.endElement("", "", ReportXMLConst.ELEMENT_REPORTLET);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportException.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportException.java
new file mode 100644
index 0000000..b8d311b
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.logic.report;
+
+public class ReportException extends RuntimeException {
+
+    private static final long serialVersionUID = 6719507778589395283L;
+
+    public ReportException(final Throwable cause) {
+        super(cause);
+    }
+
+    public ReportException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportJob.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportJob.java
new file mode 100644
index 0000000..ca59705
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportJob.java
@@ -0,0 +1,206 @@
+/*
+ * 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.server.logic.report;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.commons.io.IOUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.persistence.api.dao.ReportDAO;
+import org.apache.syncope.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.ReportExec;
+import org.apache.syncope.server.logic.data.ReportDataBinder;
+import org.apache.syncope.server.spring.ApplicationContextProvider;
+import org.apache.syncope.server.utils.ExceptionUtil;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Quartz job for executing a given report.
+ */
+@SuppressWarnings("unchecked")
+@DisallowConcurrentExecution
+public class ReportJob implements Job {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ReportJob.class);
+
+    /**
+     * Report DAO.
+     */
+    @Autowired
+    private ReportDAO reportDAO;
+
+    /**
+     * Report execution DAO.
+     */
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    /**
+     * Report data binder.
+     */
+    @Autowired
+    private ReportDataBinder dataBinder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    /**
+     * Key, set by the caller, for identifying the report to be executed.
+     */
+    private Long reportKey;
+
+    /**
+     * Report id setter.
+     *
+     * @param reportKey to be set
+     */
+    public void setReportKey(final Long reportKey) {
+        this.reportKey = reportKey;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void execute(final JobExecutionContext context) throws JobExecutionException {
+        Report report = reportDAO.find(reportKey);
+        if (report == null) {
+            throw new JobExecutionException("Report " + reportKey + " not found");
+        }
+
+        // 1. create execution
+        ReportExec execution = entityFactory.newEntity(ReportExec.class);
+        execution.setStatus(ReportExecStatus.STARTED);
+        execution.setStartDate(new Date());
+        execution.setReport(report);
+        execution = reportExecDAO.save(execution);
+
+        report.addExec(execution);
+        report = reportDAO.save(report);
+
+        // 2. define a SAX handler for generating result as XML
+        TransformerHandler handler;
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zos = new ZipOutputStream(baos);
+        zos.setLevel(Deflater.BEST_COMPRESSION);
+        try {
+            SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+            handler = tFactory.newTransformerHandler();
+            Transformer serializer = handler.getTransformer();
+            serializer.setOutputProperty(OutputKeys.ENCODING, SyncopeConstants.DEFAULT_ENCODING);
+            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+            // a single ZipEntry in the ZipOutputStream
+            zos.putNextEntry(new ZipEntry(report.getName()));
+
+            // streaming SAX handler in a compressed byte array stream
+            handler.setResult(new StreamResult(zos));
+        } catch (Exception e) {
+            throw new JobExecutionException("While configuring for SAX generation", e, true);
+        }
+
+        execution.setStatus(ReportExecStatus.RUNNING);
+        execution = reportExecDAO.save(execution);
+
+        // 3. actual report execution
+        StringBuilder reportExecutionMessage = new StringBuilder();
+        try {
+            // report header
+            handler.startDocument();
+            AttributesImpl atts = new AttributesImpl();
+            atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
+            handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
+
+            // iterate over reportlet instances defined for this report
+            for (ReportletConf reportletConf : report.getReportletConfs()) {
+                Class<Reportlet> reportletClass =
+                        dataBinder.findReportletClassHavingConfClass(reportletConf.getClass());
+                if (reportletClass != null) {
+                    Reportlet<ReportletConf> autowired =
+                            (Reportlet<ReportletConf>) ApplicationContextProvider.getBeanFactory().
+                            createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                    autowired.setConf(reportletConf);
+
+                    // invoke reportlet
+                    try {
+                        autowired.extract(handler);
+                    } catch (Exception e) {
+                        execution.setStatus(ReportExecStatus.FAILURE);
+
+                        Throwable t = e instanceof ReportException
+                                ? e.getCause()
+                                : e;
+                        reportExecutionMessage.
+                                append(ExceptionUtil.getFullStackTrace(t)).
+                                append("\n==================\n");
+                    }
+                }
+            }
+
+            // report footer
+            handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
+            handler.endDocument();
+
+            if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
+                execution.setStatus(ReportExecStatus.SUCCESS);
+            }
+        } catch (Exception e) {
+            execution.setStatus(ReportExecStatus.FAILURE);
+            reportExecutionMessage.append(ExceptionUtil.getFullStackTrace(e));
+
+            throw new JobExecutionException(e, true);
+        } finally {
+            try {
+                zos.closeEntry();
+                IOUtils.closeQuietly(zos);
+                IOUtils.closeQuietly(baos);
+            } catch (IOException e) {
+                LOG.error("While closing StreamResult's backend", e);
+            }
+
+            execution.setExecResult(baos.toByteArray());
+            execution.setMessage(reportExecutionMessage.toString());
+            execution.setEndDate(new Date());
+            reportExecDAO.save(execution);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportXMLConst.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportXMLConst.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportXMLConst.java
new file mode 100644
index 0000000..3cd22cd
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportXMLConst.java
@@ -0,0 +1,44 @@
+/*
+ * 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.server.logic.report;
+
+public final class ReportXMLConst {
+
+    public static final String XSD_STRING = "xsd:string";
+
+    public static final String XSD_INT = "xsd:integer";
+
+    public static final String XSD_LONG = "xsd:long";
+
+    public static final String XSD_BOOLEAN = "xsd:boolean";
+
+    public static final String XSD_DATETIME = "xsd:dateTime";
+
+    public static final String ELEMENT_REPORT = "report";
+
+    public static final String ATTR_NAME = "name";
+
+    public static final String ATTR_CLASS = "class";
+
+    public static final String ELEMENT_REPORTLET = "reportlet";
+
+    private ReportXMLConst() {
+        // empty private constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/Reportlet.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/Reportlet.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/Reportlet.java
new file mode 100644
index 0000000..49438bc
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/Reportlet.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.logic.report;
+
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * Interface for all elements that can be embedded in a report.
+ *
+ * @see org.apache.syncope.core.persistence.beans.Report
+ */
+public interface Reportlet<T extends ReportletConf> {
+
+    /**
+     * Set this reportlet configuration.
+     *
+     * @param conf configuration
+     */
+    void setConf(T conf);
+
+    /**
+     * Actual data extraction for reporting.
+     *
+     * @param handler SAX content handler for streaming result
+     * @throws SAXException if there is any problem in SAX handling
+     * @throws ReportException if anything goes wrong
+     */
+    void extract(ContentHandler handler) throws SAXException, ReportException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportletConfClass.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportletConfClass.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportletConfClass.java
new file mode 100644
index 0000000..48896cc
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/ReportletConfClass.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.logic.report;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.syncope.common.lib.report.ReportletConf;
+
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReportletConfClass {
+
+    Class<? extends ReportletConf> value();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/RoleReportlet.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/RoleReportlet.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/RoleReportlet.java
new file mode 100644
index 0000000..3ffbdfb
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/RoleReportlet.java
@@ -0,0 +1,327 @@
+/*
+ * 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.server.logic.report;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.report.RoleReportletConf;
+import org.apache.syncope.common.lib.report.RoleReportletConf.Feature;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.server.logic.data.RoleDataBinder;
+import org.apache.syncope.server.logic.search.SearchCondConverter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(RoleReportletConf.class)
+public class RoleReportlet extends AbstractReportlet<RoleReportletConf> {
+
+    private static final int PAGE_SIZE = 10;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    @Autowired
+    private RoleDataBinder roleDataBinder;
+
+    private List<Role> getPagedRoles(final int page) {
+        final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+        final List<Role> result;
+        if (StringUtils.isBlank(conf.getMatchingCond())) {
+            result = roleDAO.findAll();
+        } else {
+            result = searchDAO.search(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()),
+                    page, PAGE_SIZE, Collections.<OrderByClause>emptyList(), SubjectType.ROLE);
+        }
+
+        return result;
+    }
+
+    private int count() {
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+
+        return StringUtils.isBlank(conf.getMatchingCond())
+                ? roleDAO.findAll().size()
+                : searchDAO.count(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), SubjectType.ROLE);
+    }
+
+    private void doExtractResources(final ContentHandler handler, final AbstractSubjectTO subjectTO)
+            throws SAXException {
+
+        if (subjectTO.getResources().isEmpty()) {
+            LOG.debug("No resources found for {}[{}]", subjectTO.getClass().getSimpleName(), subjectTO.getKey());
+        } else {
+            AttributesImpl atts = new AttributesImpl();
+            handler.startElement("", "", "resources", null);
+
+            for (String resourceName : subjectTO.getResources()) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, resourceName);
+                handler.startElement("", "", "resource", atts);
+                handler.endElement("", "", "resource");
+            }
+
+            handler.endElement("", "", "resources");
+        }
+    }
+
+    private void doExtractAttributes(final ContentHandler handler, final AbstractAttributableTO attributableTO,
+            final Collection<String> attrs, final Collection<String> derAttrs, final Collection<String> virAttrs)
+            throws SAXException {
+
+        AttributesImpl atts = new AttributesImpl();
+        if (!attrs.isEmpty()) {
+            Map<String, AttrTO> attrMap = attributableTO.getAttrMap();
+
+            handler.startElement("", "", "attributes", null);
+            for (String attrName : attrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "attribute", atts);
+
+                if (attrMap.containsKey(attrName)) {
+                    for (String value : attrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "attribute");
+            }
+            handler.endElement("", "", "attributes");
+        }
+
+        if (!derAttrs.isEmpty()) {
+            Map<String, AttrTO> derAttrMap = attributableTO.getDerAttrMap();
+
+            handler.startElement("", "", "derivedAttributes", null);
+            for (String attrName : derAttrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "derivedAttribute", atts);
+
+                if (derAttrMap.containsKey(attrName)) {
+                    for (String value : derAttrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "derivedAttribute");
+            }
+            handler.endElement("", "", "derivedAttributes");
+        }
+
+        if (!virAttrs.isEmpty()) {
+            Map<String, AttrTO> virAttrMap = attributableTO.getVirAttrMap();
+
+            handler.startElement("", "", "virtualAttributes", null);
+            for (String attrName : virAttrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "virtualAttribute", atts);
+
+                if (virAttrMap.containsKey(attrName)) {
+                    for (String value : virAttrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "virtualAttribute");
+            }
+            handler.endElement("", "", "virtualAttributes");
+        }
+    }
+
+    private void doExtract(final ContentHandler handler, final List<Role> roles)
+            throws SAXException, ReportException {
+
+        AttributesImpl atts = new AttributesImpl();
+        for (Role role : roles) {
+            atts.clear();
+
+            for (Feature feature : conf.getFeatures()) {
+                String type = null;
+                String value = null;
+                switch (feature) {
+                    case key:
+                        type = ReportXMLConst.XSD_LONG;
+                        value = String.valueOf(role.getKey());
+                        break;
+
+                    case name:
+                        type = ReportXMLConst.XSD_STRING;
+                        value = String.valueOf(role.getName());
+                        break;
+
+                    case roleOwner:
+                        type = ReportXMLConst.XSD_LONG;
+                        value = String.valueOf(role.getRoleOwner());
+                        break;
+
+                    case userOwner:
+                        type = ReportXMLConst.XSD_LONG;
+                        value = String.valueOf(role.getUserOwner());
+                        break;
+
+                    default:
+                }
+
+                if (type != null && value != null) {
+                    atts.addAttribute("", "", feature.name(), type, value);
+                }
+            }
+
+            handler.startElement("", "", "role", atts);
+
+            // Using RoleTO for attribute values, since the conversion logic of
+            // values to String is already encapsulated there
+            RoleTO roleTO = roleDataBinder.getRoleTO(role);
+
+            doExtractAttributes(handler, roleTO, conf.getAttrs(), conf.getDerAttrs(), conf.getVirAttrs());
+
+            if (conf.getFeatures().contains(Feature.entitlements)) {
+                handler.startElement("", "", "entitlements", null);
+
+                for (String ent : roleTO.getEntitlements()) {
+                    atts.clear();
+
+                    atts.addAttribute("", "", "id", ReportXMLConst.XSD_STRING, String.valueOf(ent));
+
+                    handler.startElement("", "", "entitlement", atts);
+                    handler.endElement("", "", "entitlement");
+                }
+
+                handler.endElement("", "", "entitlements");
+            }
+            // to get resources associated to a role
+            if (conf.getFeatures().contains(Feature.resources)) {
+                doExtractResources(handler, roleTO);
+            }
+            //to get users asscoiated to a role is preferred RoleDAO to RoleTO
+            if (conf.getFeatures().contains(Feature.users)) {
+                handler.startElement("", "", "users", null);
+
+                for (Membership memb : roleDAO.findMemberships(role)) {
+                    atts.clear();
+
+                    atts.addAttribute("", "", "key", ReportXMLConst.XSD_LONG,
+                            String.valueOf(memb.getUser().getKey()));
+                    atts.addAttribute("", "", "username", ReportXMLConst.XSD_STRING,
+                            String.valueOf(memb.getUser().getUsername()));
+
+                    handler.startElement("", "", "user", atts);
+                    handler.endElement("", "", "user");
+                }
+
+                handler.endElement("", "", "users");
+            }
+
+            handler.endElement("", "", "role");
+        }
+    }
+
+    private void doExtractConf(final ContentHandler handler) throws SAXException {
+        if (conf == null) {
+            LOG.debug("Report configuration is not present");
+        }
+
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement("", "", "configurations", null);
+        handler.startElement("", "", "roleAttributes", atts);
+
+        for (Feature feature : conf.getFeatures()) {
+            atts.clear();
+            handler.startElement("", "", "feature", atts);
+            handler.characters(feature.name().toCharArray(), 0, feature.name().length());
+            handler.endElement("", "", "feature");
+        }
+
+        for (String attr : conf.getAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "attribute", atts);
+            handler.characters(attr.toCharArray(), 0, attr.length());
+            handler.endElement("", "", "attribute");
+        }
+
+        for (String derAttr : conf.getDerAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "derAttribute", atts);
+            handler.characters(derAttr.toCharArray(), 0, derAttr.length());
+            handler.endElement("", "", "derAttribute");
+        }
+
+        for (String virAttr : conf.getVirAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "virAttribute", atts);
+            handler.characters(virAttr.toCharArray(), 0, virAttr.length());
+            handler.endElement("", "", "virAttribute");
+        }
+
+        handler.endElement("", "", "roleAttributes");
+        handler.endElement("", "", "configurations");
+    }
+
+    @Override
+    protected void doExtract(final ContentHandler handler) throws SAXException, ReportException {
+        doExtractConf(handler);
+        for (int i = 1; i <= (count() / PAGE_SIZE) + 1; i++) {
+            doExtract(handler, getPagedRoles(i));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/StaticReportlet.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/StaticReportlet.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/StaticReportlet.java
new file mode 100644
index 0000000..bdc76f8
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/StaticReportlet.java
@@ -0,0 +1,120 @@
+/*
+ * 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.server.logic.report;
+
+import org.apache.syncope.common.lib.report.StaticReportletConf;
+import org.apache.syncope.server.utils.DataFormat;
+import org.springframework.util.StringUtils;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(StaticReportletConf.class)
+public class StaticReportlet extends AbstractReportlet<StaticReportletConf> {
+
+    private void doExtractConf(final ContentHandler handler) throws SAXException {
+
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement("", "", "configurations", null);
+        handler.startElement("", "", "staticAttributes", atts);
+
+        handler.startElement("", "", "string", atts);
+        handler.characters("string".toCharArray(), 0, "string".length());
+        handler.endElement("", "", "string");
+
+        handler.startElement("", "", "long", atts);
+        handler.characters("long".toCharArray(), 0, "long".length());
+        handler.endElement("", "", "long");
+
+        handler.startElement("", "", "double", atts);
+        handler.characters("double".toCharArray(), 0, "double".length());
+        handler.endElement("", "", "double");
+
+        handler.startElement("", "", "date", atts);
+        handler.characters("date".toCharArray(), 0, "date".length());
+        handler.endElement("", "", "date");
+
+        handler.startElement("", "", "double", atts);
+        handler.characters("double".toCharArray(), 0, "double".length());
+        handler.endElement("", "", "double");
+
+        handler.startElement("", "", "enum", atts);
+        handler.characters("enum".toCharArray(), 0, "enum".length());
+        handler.endElement("", "", "enum");
+
+        handler.startElement("", "", "list", atts);
+        handler.characters("list".toCharArray(), 0, "list".length());
+        handler.endElement("", "", "list");
+
+        handler.endElement("", "", "staticAttributes");
+        handler.endElement("", "", "configurations");
+    }
+
+    @Override
+    public void doExtract(final ContentHandler handler) throws SAXException, ReportException {
+
+        doExtractConf(handler);
+
+        if (StringUtils.hasText(conf.getStringField())) {
+            handler.startElement("", "", "string", null);
+            handler.characters(conf.getStringField().toCharArray(), 0, conf.getStringField().length());
+            handler.endElement("", "", "string");
+        }
+
+        if (conf.getLongField() != null) {
+            handler.startElement("", "", "long", null);
+            String printed = String.valueOf(conf.getLongField());
+            handler.characters(printed.toCharArray(), 0, printed.length());
+            handler.endElement("", "", "long");
+        }
+
+        if (conf.getDoubleField() != null) {
+            handler.startElement("", "", "double", null);
+            String printed = String.valueOf(conf.getDoubleField());
+            handler.characters(printed.toCharArray(), 0, printed.length());
+            handler.endElement("", "", "double");
+        }
+
+        if (conf.getDateField() != null) {
+            handler.startElement("", "", "date", null);
+            String printed = DataFormat.format(conf.getDateField());
+            handler.characters(printed.toCharArray(), 0, printed.length());
+            handler.endElement("", "", "date");
+        }
+
+        if (conf.getTraceLevel() != null) {
+            handler.startElement("", "", "enum", null);
+            String printed = conf.getTraceLevel().name();
+            handler.characters(printed.toCharArray(), 0, printed.length());
+            handler.endElement("", "", "enum");
+        }
+
+        if (conf.getListField() != null && !conf.getListField().isEmpty()) {
+            handler.startElement("", "", "list", null);
+            for (String item : conf.getListField()) {
+                if (StringUtils.hasText(item)) {
+                    handler.startElement("", "", "string", null);
+                    handler.characters(item.toCharArray(), 0, item.length());
+                    handler.endElement("", "", "string");
+                }
+            }
+            handler.endElement("", "", "list");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/TextSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/TextSerializer.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/TextSerializer.java
new file mode 100644
index 0000000..8ad63fc
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/TextSerializer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.logic.report;
+
+import org.apache.cocoon.sax.component.XMLSerializer;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * Converts XML into plain text. It omits all XML tags and writes only character events to the output. Input document
+ * must have at least one element - root element - which should wrap all the text inside it.
+ *
+ */
+public class TextSerializer extends XMLSerializer {
+
+    private static final String UTF_8 = "UTF-8";
+
+    private static final String TXT = "text";
+
+    public TextSerializer() {
+        super();
+        super.setOmitXmlDeclaration(true);
+    }
+
+    @Override
+    public void setDocumentLocator(final Locator locator) {
+        // nothing
+    }
+
+    @Override
+    public void processingInstruction(final String target, final String data)
+            throws SAXException {
+        // nothing
+    }
+
+    @Override
+    public void startDTD(final String name, final String publicId, final String systemId)
+            throws SAXException {
+        // nothing
+    }
+
+    @Override
+    public void endDTD() throws SAXException {
+        // nothing
+    }
+
+    @Override
+    public void startElement(final String uri, final String loc, final String raw, final Attributes atts)
+            throws SAXException {
+        // nothing
+    }
+
+    @Override
+    public void endElement(final String uri, final String name, final String raw)
+            throws SAXException {
+        // nothing
+    }
+
+    @Override
+    public void endDocument() throws SAXException {
+        super.endDocument();
+    }
+
+    /**
+     * @throws SAXException if text is encountered before root element.
+     */
+    @Override
+    public void characters(final char buffer[], final int start, final int len) throws SAXException {
+        super.characters(buffer, start, len);
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+    }
+
+    public static TextSerializer createPlainSerializer() {
+        final TextSerializer serializer = new TextSerializer();
+        serializer.setContentType("text/plain; charset=" + UTF_8);
+        serializer.setEncoding(UTF_8);
+        serializer.setMethod(TXT);
+        return serializer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/UserReportlet.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/UserReportlet.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/UserReportlet.java
new file mode 100644
index 0000000..ecae604
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/report/UserReportlet.java
@@ -0,0 +1,359 @@
+/*
+ * 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.server.logic.report;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.report.UserReportletConf;
+import org.apache.syncope.common.lib.report.UserReportletConf.Feature;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.server.logic.data.RoleDataBinder;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.logic.search.SearchCondConverter;
+import org.apache.syncope.server.utils.DataFormat;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(UserReportletConf.class)
+public class UserReportlet extends AbstractReportlet<UserReportletConf> {
+
+    private static final int PAGE_SIZE = 10;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    @Autowired
+    private UserDataBinder userDataBinder;
+
+    @Autowired
+    private RoleDataBinder roleDataBinder;
+
+    private List<User> getPagedUsers(final int page) {
+        final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+
+        final List<User> result;
+        if (StringUtils.isBlank(conf.getMatchingCond())) {
+            result = userDAO.findAll(adminRoleIds, page, PAGE_SIZE);
+        } else {
+            result = searchDAO.search(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()),
+                    page, PAGE_SIZE, Collections.<OrderByClause>emptyList(), SubjectType.USER);
+        }
+
+        return result;
+    }
+
+    private int count() {
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+
+        return StringUtils.isBlank(conf.getMatchingCond())
+                ? userDAO.count(adminRoleIds)
+                : searchDAO.count(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), SubjectType.USER);
+    }
+
+    private void doExtractResources(final ContentHandler handler, final AbstractSubjectTO subjectTO)
+            throws SAXException {
+
+        if (subjectTO.getResources().isEmpty()) {
+            LOG.debug("No resources found for {}[{}]", subjectTO.getClass().getSimpleName(), subjectTO.getKey());
+        } else {
+            AttributesImpl atts = new AttributesImpl();
+            handler.startElement("", "", "resources", null);
+
+            for (String resourceName : subjectTO.getResources()) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, resourceName);
+                handler.startElement("", "", "resource", atts);
+                handler.endElement("", "", "resource");
+            }
+
+            handler.endElement("", "", "resources");
+        }
+    }
+
+    private void doExtractAttributes(final ContentHandler handler, final AbstractAttributableTO attributableTO,
+            final Collection<String> attrs, final Collection<String> derAttrs, final Collection<String> virAttrs)
+            throws SAXException {
+
+        AttributesImpl atts = new AttributesImpl();
+        if (!attrs.isEmpty()) {
+            Map<String, AttrTO> attrMap = attributableTO.getAttrMap();
+
+            handler.startElement("", "", "attributes", null);
+            for (String attrName : attrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "attribute", atts);
+
+                if (attrMap.containsKey(attrName)) {
+                    for (String value : attrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "attribute");
+            }
+            handler.endElement("", "", "attributes");
+        }
+
+        if (!derAttrs.isEmpty()) {
+            Map<String, AttrTO> derAttrMap = attributableTO.getDerAttrMap();
+
+            handler.startElement("", "", "derivedAttributes", null);
+            for (String attrName : derAttrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "derivedAttribute", atts);
+
+                if (derAttrMap.containsKey(attrName)) {
+                    for (String value : derAttrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "derivedAttribute");
+            }
+            handler.endElement("", "", "derivedAttributes");
+        }
+
+        if (!virAttrs.isEmpty()) {
+            Map<String, AttrTO> virAttrMap = attributableTO.getVirAttrMap();
+
+            handler.startElement("", "", "virtualAttributes", null);
+            for (String attrName : virAttrs) {
+                atts.clear();
+
+                atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+                handler.startElement("", "", "virtualAttribute", atts);
+
+                if (virAttrMap.containsKey(attrName)) {
+                    for (String value : virAttrMap.get(attrName).getValues()) {
+                        handler.startElement("", "", "value", null);
+                        handler.characters(value.toCharArray(), 0, value.length());
+                        handler.endElement("", "", "value");
+                    }
+                } else {
+                    LOG.debug("{} not found for {}[{}]", attrName,
+                            attributableTO.getClass().getSimpleName(), attributableTO.getKey());
+                }
+
+                handler.endElement("", "", "virtualAttribute");
+            }
+            handler.endElement("", "", "virtualAttributes");
+        }
+    }
+
+    private void doExtract(final ContentHandler handler, final List<User> users)
+            throws SAXException, ReportException {
+
+        AttributesImpl atts = new AttributesImpl();
+        for (User user : users) {
+            atts.clear();
+
+            for (Feature feature : conf.getFeatures()) {
+                String type = null;
+                String value = null;
+                switch (feature) {
+                    case key:
+                        type = ReportXMLConst.XSD_LONG;
+                        value = String.valueOf(user.getKey());
+                        break;
+
+                    case username:
+                        type = ReportXMLConst.XSD_STRING;
+                        value = user.getUsername();
+                        break;
+
+                    case workflowId:
+                        type = ReportXMLConst.XSD_LONG;
+                        value = String.valueOf(user.getWorkflowId());
+                        break;
+
+                    case status:
+                        type = ReportXMLConst.XSD_STRING;
+                        value = user.getStatus();
+                        break;
+
+                    case creationDate:
+                        type = ReportXMLConst.XSD_DATETIME;
+                        value = user.getCreationDate() == null
+                                ? ""
+                                : DataFormat.format(user.getCreationDate());
+                        break;
+
+                    case lastLoginDate:
+                        type = ReportXMLConst.XSD_DATETIME;
+                        value = user.getLastLoginDate() == null
+                                ? ""
+                                : DataFormat.format(user.getLastLoginDate());
+                        break;
+
+                    case changePwdDate:
+                        type = ReportXMLConst.XSD_DATETIME;
+                        value = user.getChangePwdDate() == null
+                                ? ""
+                                : DataFormat.format(user.getChangePwdDate());
+                        break;
+
+                    case passwordHistorySize:
+                        type = ReportXMLConst.XSD_INT;
+                        value = String.valueOf(user.getPasswordHistory().size());
+                        break;
+
+                    case failedLoginCount:
+                        type = ReportXMLConst.XSD_INT;
+                        value = String.valueOf(user.getFailedLogins());
+                        break;
+
+                    default:
+                }
+
+                if (type != null && value != null) {
+                    atts.addAttribute("", "", feature.name(), type, value);
+                }
+            }
+
+            handler.startElement("", "", "user", atts);
+
+            // Using UserTO for attribute values, since the conversion logic of
+            // values to String is already encapsulated there
+            UserTO userTO = userDataBinder.getUserTO(user);
+
+            doExtractAttributes(handler, userTO, conf.getAttrs(), conf.getDerAttrs(), conf.getVirAttrs());
+
+            if (conf.getFeatures().contains(Feature.memberships)) {
+                handler.startElement("", "", "memberships", null);
+
+                for (MembershipTO memb : userTO.getMemberships()) {
+                    atts.clear();
+
+                    atts.addAttribute("", "", "id", ReportXMLConst.XSD_LONG, String.valueOf(memb.getKey()));
+                    atts.addAttribute("", "", "roleId", ReportXMLConst.XSD_LONG, String.valueOf(memb.getRoleId()));
+                    atts.addAttribute("", "", "roleName", ReportXMLConst.XSD_STRING, String.valueOf(memb.getRoleName()));
+                    handler.startElement("", "", "membership", atts);
+
+                    doExtractAttributes(handler, memb, memb.getAttrMap().keySet(), memb.getDerAttrMap()
+                            .keySet(), memb.getVirAttrMap().keySet());
+
+                    if (conf.getFeatures().contains(Feature.resources)) {
+                        Membership actualMemb = user.getMembership(memb.getRoleId());
+                        if (actualMemb == null) {
+                            LOG.warn("Unexpected: cannot find membership for role {} for user {}", memb.getRoleId(),
+                                    user);
+                        } else {
+                            doExtractResources(handler, roleDataBinder.getRoleTO(actualMemb.getRole()));
+                        }
+                    }
+
+                    handler.endElement("", "", "membership");
+                }
+
+                handler.endElement("", "", "memberships");
+            }
+
+            if (conf.getFeatures().contains(Feature.resources)) {
+                doExtractResources(handler, userTO);
+            }
+
+            handler.endElement("", "", "user");
+        }
+    }
+
+    private void doExtractConf(final ContentHandler handler) throws SAXException {
+
+        AttributesImpl atts = new AttributesImpl();
+        handler.startElement("", "", "configurations", null);
+        handler.startElement("", "", "userAttributes", atts);
+
+        for (Feature feature : conf.getFeatures()) {
+            atts.clear();
+            handler.startElement("", "", "feature", atts);
+            handler.characters(feature.name().toCharArray(), 0, feature.name().length());
+            handler.endElement("", "", "feature");
+        }
+
+        for (String attr : conf.getAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "attribute", atts);
+            handler.characters(attr.toCharArray(), 0, attr.length());
+            handler.endElement("", "", "attribute");
+        }
+
+        for (String derAttr : conf.getDerAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "derAttribute", atts);
+            handler.characters(derAttr.toCharArray(), 0, derAttr.length());
+            handler.endElement("", "", "derAttribute");
+        }
+
+        for (String virAttr : conf.getVirAttrs()) {
+            atts.clear();
+            handler.startElement("", "", "virAttribute", atts);
+            handler.characters(virAttr.toCharArray(), 0, virAttr.length());
+            handler.endElement("", "", "virAttribute");
+        }
+
+        handler.endElement("", "", "userAttributes");
+        handler.endElement("", "", "configurations");
+    }
+
+    @Override
+    protected void doExtract(final ContentHandler handler) throws SAXException, ReportException {
+        doExtractConf(handler);
+        for (int i = 1; i <= (count() / PAGE_SIZE) + 1; i++) {
+            doExtract(handler, getPagedUsers(i));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondConverter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondConverter.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondConverter.java
new file mode 100644
index 0000000..662cd46
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondConverter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.logic.search;
+
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+import org.apache.syncope.common.lib.search.SyncopeFiqlSearchConditionBuilder;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+
+/**
+ * Converts FIQL expressions to Syncope's <tt>SearchCond</tt>.
+ */
+public final class SearchCondConverter {
+
+    /**
+     * Parses a FIQL expression into Syncope's <tt>SearchCond</tt>, using CXF's <tt>FiqlParser</tt>.
+     *
+     * @param fiqlExpression FIQL string
+     * @return <tt>SearchCond</tt> instance for given FIQL expression
+     * @see FiqlParser
+     */
+    public static SearchCond convert(final String fiqlExpression) {
+        FiqlParser<SearchBean> fiqlParser = new FiqlParser<SearchBean>(
+                SearchBean.class, SyncopeFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
+        SearchCondVisitor searchCondVisitor = new SearchCondVisitor();
+
+        searchCondVisitor.visit(fiqlParser.parse(fiqlExpression));
+        return searchCondVisitor.getQuery();
+    }
+
+    private SearchCondConverter() {
+        // empty constructor for static utility class        
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondVisitor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondVisitor.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondVisitor.java
new file mode 100644
index 0000000..5c9acdb
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/search/SearchCondVisitor.java
@@ -0,0 +1,203 @@
+/*
+ * 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.server.logic.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.cxf.jaxrs.ext.search.ConditionType;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchUtils;
+import org.apache.cxf.jaxrs.ext.search.visitor.AbstractSearchConditionVisitor;
+import org.apache.syncope.common.lib.search.SearchableFields;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.persistence.api.dao.search.EntitlementCond;
+import org.apache.syncope.persistence.api.dao.search.MembershipCond;
+import org.apache.syncope.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.dao.search.SubjectCond;
+
+/**
+ * Converts CXF's <tt>SearchCondition</tt> into internal <tt>SearchCond</tt>.
+ */
+public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
+
+    private static final List<String> ATTRIBUTABLE_FIELDS;
+
+    static {
+        ATTRIBUTABLE_FIELDS = new ArrayList<String>();
+        ATTRIBUTABLE_FIELDS.addAll(SearchableFields.get(UserTO.class));
+        ATTRIBUTABLE_FIELDS.addAll(SearchableFields.get(RoleTO.class));
+    }
+
+    private SearchCond searchCond;
+
+    public SearchCondVisitor() {
+        super(null);
+    }
+
+    public SearchCondVisitor(final Map<String, String> fieldMap) {
+        super(fieldMap);
+    }
+
+    private AttributeCond createAttributeCond(final String schema) {
+        AttributeCond attributeCond = ATTRIBUTABLE_FIELDS.contains(schema)
+                ? new SubjectCond()
+                : new AttributeCond();
+        attributeCond.setSchema(schema);
+        return attributeCond;
+    }
+
+    private SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
+        String name = getRealPropertyName(sc.getStatement().getProperty());
+        SpecialAttr specialAttrName = SpecialAttr.fromString(name);
+
+        String value = SearchUtils.toSqlWildcardString(sc.getStatement().getValue().toString(), false).
+                replaceAll("\\\\_", "_");
+        SpecialAttr specialAttrValue = SpecialAttr.fromString(value);
+
+        AttributeCond attributeCond = createAttributeCond(name);
+        attributeCond.setExpression(value);
+
+        SearchCond leaf;
+        switch (sc.getConditionType()) {
+            case EQUALS:
+            case NOT_EQUALS:
+                if (specialAttrName == null) {
+                    if (specialAttrValue != null && specialAttrValue == SpecialAttr.NULL) {
+                        attributeCond.setType(AttributeCond.Type.ISNULL);
+                        attributeCond.setExpression(null);
+                    } else if (value.indexOf('%') == -1) {
+                        attributeCond.setType(AttributeCond.Type.EQ);
+                    } else {
+                        attributeCond.setType(AttributeCond.Type.LIKE);
+                    }
+
+                    leaf = SearchCond.getLeafCond(attributeCond);
+                } else {
+                    switch (specialAttrName) {
+                        case ROLES:
+                            MembershipCond membershipCond = new MembershipCond();
+                            membershipCond.setRoleId(Long.valueOf(value));
+                            leaf = SearchCond.getLeafCond(membershipCond);
+                            break;
+
+                        case RESOURCES:
+                            ResourceCond resourceCond = new ResourceCond();
+                            resourceCond.setResourceName(value);
+                            leaf = SearchCond.getLeafCond(resourceCond);
+                            break;
+
+                        case ENTITLEMENTS:
+                            EntitlementCond entitlementCond = new EntitlementCond();
+                            entitlementCond.setExpression(value);
+                            leaf = SearchCond.getLeafCond(entitlementCond);
+                            break;
+
+                        default:
+                            throw new IllegalArgumentException(
+                                    String.format("Special attr name %s is not supported", specialAttrName));
+                    }
+                }
+                if (sc.getConditionType() == ConditionType.NOT_EQUALS) {
+                    if (leaf.getAttributeCond() != null
+                            && leaf.getAttributeCond().getType() == AttributeCond.Type.ISNULL) {
+
+                        leaf.getAttributeCond().setType(AttributeCond.Type.ISNOTNULL);
+                    } else if (leaf.getSubjectCond() != null
+                            && leaf.getSubjectCond().getType() == SubjectCond.Type.ISNULL) {
+
+                        leaf.getSubjectCond().setType(AttributeCond.Type.ISNOTNULL);
+                    } else {
+                        leaf = SearchCond.getNotLeafCond(leaf);
+                    }
+                }
+                break;
+
+            case GREATER_OR_EQUALS:
+                attributeCond.setType(AttributeCond.Type.GE);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case GREATER_THAN:
+                attributeCond.setType(AttributeCond.Type.GT);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case LESS_OR_EQUALS:
+                attributeCond.setType(AttributeCond.Type.LE);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case LESS_THAN:
+                attributeCond.setType(AttributeCond.Type.LT);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            default:
+                throw new IllegalArgumentException(
+                        String.format("Condition type %s is not supported", sc.getConditionType().name()));
+        }
+
+        return leaf;
+    }
+
+    private SearchCond visitCompount(final SearchCondition<SearchBean> sc) {
+        List<SearchCond> searchConds = new ArrayList<SearchCond>();
+        for (SearchCondition<SearchBean> searchCondition : sc.getSearchConditions()) {
+            searchConds.add(searchCondition.getStatement() == null
+                    ? visitCompount(searchCondition)
+                    : visitPrimitive(searchCondition));
+        }
+
+        SearchCond compound;
+        switch (sc.getConditionType()) {
+            case AND:
+                compound = SearchCond.getAndCond(searchConds);
+                break;
+
+            case OR:
+                compound = SearchCond.getOrCond(searchConds);
+                break;
+
+            default:
+                throw new IllegalArgumentException(
+                        String.format("Condition type %s is not supported", sc.getConditionType().name()));
+        }
+
+        return compound;
+    }
+
+    @Override
+    public void visit(final SearchCondition<SearchBean> sc) {
+        searchCond = sc.getStatement() == null
+                ? visitCompount(sc)
+                : visitPrimitive(sc);
+    }
+
+    @Override
+    public SearchCond getQuery() {
+        return searchCond;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/DuplicateException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/DuplicateException.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/DuplicateException.java
new file mode 100644
index 0000000..517d64d
--- /dev/null
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/DuplicateException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.persistence.api.dao;
+
+/**
+ * Thrown when something is not found.
+ */
+public class DuplicateException extends RuntimeException {
+
+    private static final long serialVersionUID = -8200698688516957508L;
+
+    public DuplicateException(final String msg) {
+        super(msg);
+    }
+
+    public DuplicateException(final String msg, final Exception cause) {
+        super(msg, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/RoleDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/RoleDAO.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/RoleDAO.java
index b2d7dd3..d26615d 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/RoleDAO.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/RoleDAO.java
@@ -77,4 +77,6 @@ public interface RoleDAO extends SubjectDAO<RPlainAttr, RDerAttr, RVirAttr> {
     void delete(Role role);
 
     void delete(Long key);
+    
+    Role authFetchRole(Long key);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/UserDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/UserDAO.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/UserDAO.java
index 42ea10a..0f359c3 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/UserDAO.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/dao/UserDAO.java
@@ -62,4 +62,7 @@ public interface UserDAO extends SubjectDAO<UPlainAttr, UDerAttr, UVirAttr> {
 
     void delete(User user);
 
+    User authFecthUser(Long key);
+
+    User authFecthUser(String username);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtil.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtil.java
index ff1679c..3d12ec9 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtil.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtil.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.persistence.api.entity;
 
 import java.util.List;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
 import org.apache.syncope.common.lib.types.AttributableType;
 import org.apache.syncope.common.lib.types.IntMappingType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -83,4 +85,7 @@ public interface AttributableUtil {
 
     <T extends MappingItem> Class<T> mappingItemClass();
 
+    <T extends AbstractAttributableTO> T newAttributableTO();
+
+    <T extends AbstractSubjectTO> T newSubjectTO();
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtilFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtilFactory.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtilFactory.java
new file mode 100644
index 0000000..1f42c17
--- /dev/null
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/AttributableUtilFactory.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.persistence.api.entity;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+
+public interface AttributableUtilFactory {
+
+    AttributableUtil getInstance(AttributableType type);
+
+    AttributableUtil getInstance(String attributableType);
+
+    AttributableUtil getInstance(ObjectClass objectClass);
+
+    AttributableUtil getInstance(Attributable<?, ?, ?> attributable);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/Entity.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/Entity.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/Entity.java
index b1b4e00..6c8afbd 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/Entity.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/Entity.java
@@ -18,7 +18,9 @@
  */
 package org.apache.syncope.persistence.api.entity;
 
-public interface Entity<KEY> {
+import java.io.Serializable;
+
+public interface Entity<KEY> extends Serializable {
 
     KEY getKey();
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/EntityFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/EntityFactory.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/EntityFactory.java
index 58af4f8..c36030b 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/EntityFactory.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/EntityFactory.java
@@ -23,4 +23,6 @@ public interface EntityFactory {
     <KEY, T extends Entity<KEY>> T newEntity(Class<T> reference);
 
     <T extends Policy> T newPolicy(Class<T> reference, boolean global);
+
+    ConnPoolConf newConnPoolConf();
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/ExternalResource.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/ExternalResource.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/ExternalResource.java
index 965c1a5..8e55c26 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/ExternalResource.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/ExternalResource.java
@@ -27,7 +27,7 @@ import org.apache.syncope.common.lib.types.PropagationMode;
 import org.apache.syncope.common.lib.types.TraceLevel;
 import org.identityconnectors.framework.common.objects.SyncToken;
 
-public interface ExternalResource extends Entity<String> {
+public interface ExternalResource extends AnnotatedEntity<String> {
 
     AccountPolicy getAccountPolicy();
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/role/Role.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/role/Role.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/role/Role.java
index 0c3ac50..b61afdc 100644
--- a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/role/Role.java
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/role/Role.java
@@ -92,7 +92,7 @@ public interface Role extends Subject<RPlainAttr, RDerAttr, RVirAttr> {
 
     boolean isInheritAccountPolicy();
 
-    boolean isInheritAttrs();
+    boolean isInheritPlainAttrs();
 
     boolean isInheritDerAttrs();
 
@@ -108,7 +108,7 @@ public interface Role extends Subject<RPlainAttr, RDerAttr, RVirAttr> {
 
     void setInheritAccountPolicy(boolean condition);
 
-    void setInheritAttrs(boolean inheritAttrs);
+    void setInheritPlainAttrs(boolean inheritAttrs);
 
     void setInheritDerAttrs(boolean inheritDerAttrs);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtil.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtil.java
new file mode 100644
index 0000000..661c645
--- /dev/null
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtil.java
@@ -0,0 +1,36 @@
+/*
+ * 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.persistence.api.entity.task;
+
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.types.TaskType;
+
+public interface TaskUtil {
+
+    TaskType getType();
+
+    <T extends Task> T newTask();
+
+    <T extends AbstractTaskTO> T newTaskTO();
+
+    <T extends Task> Class<T> taskClass();
+
+    <T extends AbstractTaskTO> Class<T> taskTOClass();
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtilFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtilFactory.java b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtilFactory.java
new file mode 100644
index 0000000..b2b24e6
--- /dev/null
+++ b/syncope620/server/persistence-api/src/main/java/org/apache/syncope/persistence/api/entity/task/TaskUtilFactory.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.persistence.api.entity.task;
+
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.types.TaskType;
+
+public interface TaskUtilFactory {
+
+    TaskUtil getInstance(TaskType type);
+
+    TaskUtil getInstance(Task task);
+
+    TaskUtil getInstance(Class<? extends AbstractTaskTO> taskClass);
+
+    TaskUtil getInstance(AbstractTaskTO taskTO);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/pom.xml b/syncope620/server/persistence-jpa/pom.xml
index 17b6a92..b5f0bbe 100644
--- a/syncope620/server/persistence-jpa/pom.xml
+++ b/syncope620/server/persistence-jpa/pom.xml
@@ -91,7 +91,7 @@ under the License.
       
     <dependency>
       <groupId>org.apache.syncope.server</groupId>
-      <artifactId>syncope-provisioning-api</artifactId>
+      <artifactId>syncope-provisioning-common</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/AbstractSubjectDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/AbstractSubjectDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/AbstractSubjectDAO.java
index ac6ab68..64712c8 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/AbstractSubjectDAO.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/AbstractSubjectDAO.java
@@ -89,10 +89,10 @@ abstract class AbstractSubjectDAO<P extends PlainAttr, D extends DerAttr, V exte
         final Parser parser = new Parser(new StringReader(expression));
 
         // Schema names
-        final List<String> identifiers = new ArrayList<String>();
+        final List<String> identifiers = new ArrayList<>();
 
         // Literals
-        final List<String> literals = new ArrayList<String>();
+        final List<String> literals = new ArrayList<>();
 
         // Get schema names and literals
         Token token;
@@ -131,18 +131,18 @@ abstract class AbstractSubjectDAO<P extends PlainAttr, D extends DerAttr, V exte
         final List<String> attrValues = split(value, literals);
 
         if (attrValues.size() != identifiers.size()) {
-            LOG.error("Ambiguous jexl expression resolution.");
+            LOG.error("Ambiguous JEXL expression resolution.");
             throw new IllegalArgumentException("literals and values have different size");
         }
 
         // clauses to be used with INTERSECTed queries
-        final Set<String> clauses = new HashSet<String>();
+        final Set<String> clauses = new HashSet<>();
 
         // builder to build the clauses
         final StringBuilder bld = new StringBuilder();
 
         // Contains used identifiers in order to avoid replications
-        final Set<String> used = new HashSet<String>();
+        final Set<String> used = new HashSet<>();
 
         // Create several clauses: one for eanch identifiers
         for (int i = 0; i < identifiers.size(); i++) {


[07/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConfigurationDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConfigurationDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConfigurationDataBinder.java
new file mode 100644
index 0000000..64c4a4e
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConfigurationDataBinder.java
@@ -0,0 +1,75 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.Collections;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConfTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.conf.CPlainAttr;
+import org.apache.syncope.persistence.api.entity.conf.CPlainSchema;
+import org.apache.syncope.persistence.api.entity.conf.Conf;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ConfigurationDataBinder extends AbstractAttributableDataBinder {
+
+    public ConfTO getConfTO(final Conf conf) {
+        final ConfTO confTO = new ConfTO();
+        confTO.setKey(conf.getKey());
+
+        fillTO(confTO, conf.getPlainAttrs(),
+                conf.getDerAttrs(), conf.getVirAttrs(), Collections.<ExternalResource>emptySet());
+
+        return confTO;
+    }
+
+    public AttrTO getAttrTO(final CPlainAttr attr) {
+        final AttrTO attributeTO = new AttrTO();
+        attributeTO.setSchema(attr.getSchema().getKey());
+        attributeTO.getValues().addAll(attr.getValuesAsStrings());
+        attributeTO.setReadonly(attr.getSchema().isReadonly());
+
+        return attributeTO;
+    }
+
+    public CPlainAttr getAttribute(final AttrTO attributeTO) {
+        CPlainSchema schema = getPlainSchema(attributeTO.getSchema(), CPlainSchema.class);
+        if (schema == null) {
+            throw new NotFoundException("Conf schema " + attributeTO.getSchema());
+        } else {
+            SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+            CPlainAttr attr = entityFactory.newEntity(CPlainAttr.class);
+            attr.setSchema(schema);
+            fillAttribute(attributeTO.getValues(), attrUtilFactory.getInstance(AttributableType.CONFIGURATION),
+                    schema, attr, invalidValues);
+
+            if (!invalidValues.isEmpty()) {
+                throw invalidValues;
+            }
+            return attr;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConnInstanceDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConnInstanceDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConnInstanceDataBinder.java
new file mode 100644
index 0000000..1091be1
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ConnInstanceDataBinder.java
@@ -0,0 +1,246 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.ConnPoolConfTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ConnConfPropSchema;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.persistence.api.dao.ConnInstanceDAO;
+import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.provisioning.api.ConnIdBundleManager;
+import org.apache.syncope.provisioning.api.ConnPoolConfUtil;
+import org.identityconnectors.framework.api.ConfigurationProperties;
+import org.identityconnectors.framework.api.ConfigurationProperty;
+import org.identityconnectors.framework.impl.api.ConfigurationPropertyImpl;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ConnInstanceDataBinder {
+
+    private static final String[] IGNORE_PROPERTIES = { "key", "poolConf" };
+
+    @Autowired
+    private ConnIdBundleManager connIdBundleManager;
+
+    @Autowired
+    private ConnInstanceDAO connInstanceDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    /**
+     * Merge connector configuration properties avoiding repetition but giving priority to primary set.
+     *
+     * @param primary primary set.
+     * @param secondary secondary set.
+     * @return merged set.
+     */
+    public Set<ConnConfProperty> mergeConnConfProperties(final Set<ConnConfProperty> primary,
+            final Set<ConnConfProperty> secondary) {
+
+        final Set<ConnConfProperty> conf = new HashSet<>();
+
+        // to be used to control managed prop (needed by overridden mechanism)
+        final Set<String> propertyNames = new HashSet<>();
+
+        // get overridden connector configuration properties
+        for (ConnConfProperty prop : primary) {
+            if (!propertyNames.contains(prop.getSchema().getName())) {
+                conf.add(prop);
+                propertyNames.add(prop.getSchema().getName());
+            }
+        }
+
+        // get connector configuration properties
+        for (ConnConfProperty prop : secondary) {
+            if (!propertyNames.contains(prop.getSchema().getName())) {
+                conf.add(prop);
+                propertyNames.add(prop.getSchema().getName());
+            }
+        }
+
+        return conf;
+    }
+
+    public ConnInstance getConnInstance(final ConnInstanceTO connInstanceTO) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+
+        if (connInstanceTO.getLocation() == null) {
+            sce.getElements().add("location");
+        }
+
+        if (connInstanceTO.getBundleName() == null) {
+            sce.getElements().add("bundlename");
+        }
+
+        if (connInstanceTO.getVersion() == null) {
+            sce.getElements().add("bundleversion");
+        }
+
+        if (connInstanceTO.getConnectorName() == null) {
+            sce.getElements().add("connectorname");
+        }
+
+        if (connInstanceTO.getConfiguration() == null || connInstanceTO.getConfiguration().isEmpty()) {
+            sce.getElements().add("configuration");
+        }
+
+        ConnInstance connInstance = entityFactory.newEntity(ConnInstance.class);
+
+        BeanUtils.copyProperties(connInstanceTO, connInstance, IGNORE_PROPERTIES);
+        if (connInstanceTO.getLocation() != null) {
+            connInstance.setLocation(connInstanceTO.getLocation());
+        }
+        if (connInstanceTO.getPoolConf() != null) {
+            connInstance.setPoolConf(
+                    ConnPoolConfUtil.getConnPoolConf(connInstanceTO.getPoolConf(), entityFactory.newConnPoolConf()));
+        }
+
+        // Throw exception if there is at least one element set
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+
+        return connInstance;
+    }
+
+    public ConnInstance updateConnInstance(final long connInstanceId, final ConnInstanceTO connInstanceTO) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+
+        if (connInstanceId == 0) {
+            sce.getElements().add("connector id");
+        }
+
+        ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+        connInstance.getCapabilities().clear();
+        connInstance.getCapabilities().addAll(connInstanceTO.getCapabilities());
+
+        if (connInstanceTO.getLocation() != null) {
+            connInstance.setLocation(connInstanceTO.getLocation());
+        }
+
+        if (connInstanceTO.getBundleName() != null) {
+            connInstance.setBundleName(connInstanceTO.getBundleName());
+        }
+
+        if (connInstanceTO.getVersion() != null) {
+            connInstance.setVersion(connInstanceTO.getVersion());
+        }
+
+        if (connInstanceTO.getConnectorName() != null) {
+            connInstance.setConnectorName(connInstanceTO.getConnectorName());
+        }
+
+        if (connInstanceTO.getConfiguration() != null && !connInstanceTO.getConfiguration().isEmpty()) {
+            connInstance.setConfiguration(connInstanceTO.getConfiguration());
+        }
+
+        if (connInstanceTO.getDisplayName() != null) {
+            connInstance.setDisplayName(connInstanceTO.getDisplayName());
+        }
+
+        if (connInstanceTO.getConnRequestTimeout() != null) {
+            connInstance.setConnRequestTimeout(connInstanceTO.getConnRequestTimeout());
+        }
+
+        if (connInstanceTO.getPoolConf() == null) {
+            connInstance.setPoolConf(null);
+        } else {
+            connInstance.setPoolConf(
+                    ConnPoolConfUtil.getConnPoolConf(connInstanceTO.getPoolConf(), entityFactory.newConnPoolConf()));
+        }
+
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+
+        return connInstance;
+    }
+
+    public ConnConfPropSchema buildConnConfPropSchema(final ConfigurationProperty property) {
+        ConnConfPropSchema connConfPropSchema = new ConnConfPropSchema();
+
+        connConfPropSchema.setName(property.getName());
+        connConfPropSchema.setDisplayName(property.getDisplayName(property.getName()));
+        connConfPropSchema.setHelpMessage(property.getHelpMessage(property.getName()));
+        connConfPropSchema.setRequired(property.isRequired());
+        connConfPropSchema.setType(property.getType().getName());
+        connConfPropSchema.setOrder(((ConfigurationPropertyImpl) property).getOrder());
+        connConfPropSchema.setConfidential(property.isConfidential());
+
+        if (property.getValue() != null) {
+            if (property.getValue().getClass().isArray()) {
+                connConfPropSchema.getDefaultValues().addAll(Arrays.asList((Object[]) property.getValue()));
+            } else if (property.getValue() instanceof Collection<?>) {
+                connConfPropSchema.getDefaultValues().addAll((Collection<?>) property.getValue());
+            } else {
+                connConfPropSchema.getDefaultValues().add(property.getValue());
+            }
+        }
+
+        return connConfPropSchema;
+    }
+
+    public ConnInstanceTO getConnInstanceTO(final ConnInstance connInstance) {
+        ConnInstanceTO connInstanceTO = new ConnInstanceTO();
+        connInstanceTO.setKey(connInstance.getKey() == null ? 0L : connInstance.getKey());
+
+        // retrieve the ConfigurationProperties
+        ConfigurationProperties properties = connIdBundleManager.getConfigurationProperties(
+                connIdBundleManager.getConnectorInfo(connInstance.getLocation(),
+                        connInstance.getBundleName(), connInstance.getVersion(), connInstance.getConnectorName()));
+
+        BeanUtils.copyProperties(connInstance, connInstanceTO, IGNORE_PROPERTIES);
+
+        final Map<String, ConnConfProperty> connInstanceToConfMap = connInstanceTO.getConfigurationMap();
+
+        for (String propName : properties.getPropertyNames()) {
+            ConnConfPropSchema schema = buildConnConfPropSchema(properties.getProperty(propName));
+
+            ConnConfProperty property;
+            if (connInstanceToConfMap.containsKey(propName)) {
+                property = connInstanceToConfMap.get(propName);
+            } else {
+                property = new ConnConfProperty();
+                connInstanceTO.getConfiguration().add(property);
+            }
+
+            property.setSchema(schema);
+        }
+
+        if (connInstance.getPoolConf() != null) {
+            ConnPoolConfTO poolConf = new ConnPoolConfTO();
+            BeanUtils.copyProperties(connInstance.getPoolConf(), poolConf);
+            connInstanceTO.setPoolConf(poolConf);
+        }
+
+        return connInstanceTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/NotificationDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/NotificationDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/NotificationDataBinder.java
new file mode 100644
index 0000000..0ddb74e
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/NotificationDataBinder.java
@@ -0,0 +1,62 @@
+/*
+ * 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.server.logic.data;
+
+import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.Notification;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NotificationDataBinder {
+
+    private static final String[] IGNORE_PROPERTIES = { "key", "about", "recipients" };
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    public NotificationTO getNotificationTO(final Notification notification) {
+        NotificationTO result = new NotificationTO();
+
+        BeanUtils.copyProperties(notification, result, IGNORE_PROPERTIES);
+
+        result.setKey(notification.getKey());
+        result.setUserAbout(notification.getUserAbout());
+        result.setRoleAbout(notification.getRoleAbout());
+        result.setRecipients(notification.getRecipients());
+
+        return result;
+    }
+
+    public Notification create(final NotificationTO notificationTO) {
+        Notification result = entityFactory.newEntity(Notification.class);
+        update(result, notificationTO);
+        return result;
+    }
+
+    public void update(final Notification notification, final NotificationTO notificationTO) {
+        BeanUtils.copyProperties(notificationTO, notification, IGNORE_PROPERTIES);
+
+        notification.setUserAbout(notificationTO.getUserAbout());
+        notification.setRoleAbout(notificationTO.getRoleAbout());
+        notification.setRecipients(notificationTO.getRecipients());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/PolicyDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/PolicyDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/PolicyDataBinder.java
new file mode 100644
index 0000000..57877d2
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/PolicyDataBinder.java
@@ -0,0 +1,186 @@
+/*
+ * 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.server.logic.data;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractPolicyTO;
+import org.apache.syncope.common.lib.to.AccountPolicyTO;
+import org.apache.syncope.common.lib.to.PasswordPolicyTO;
+import org.apache.syncope.common.lib.to.SyncPolicyTO;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.SyncPolicySpec;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PolicyDataBinder {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(PolicyDataBinder.class);
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    /**
+     * Get policy TO from policy bean.
+     *
+     * @param policy bean.
+     * @return policy TO.
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractPolicyTO> T getPolicyTO(final Policy policy) {
+        final T policyTO;
+
+        switch (policy.getType()) {
+            case GLOBAL_PASSWORD:
+            case PASSWORD:
+                policyTO = (T) new PasswordPolicyTO(policy.getType().isGlobal());
+                ((PasswordPolicyTO) policyTO).setSpecification(policy.getSpecification(PasswordPolicySpec.class));
+                break;
+
+            case GLOBAL_ACCOUNT:
+            case ACCOUNT:
+                policyTO = (T) new AccountPolicyTO(policy.getType().isGlobal());
+                ((AccountPolicyTO) policyTO).setSpecification(policy.getSpecification(AccountPolicySpec.class));
+                ((AccountPolicyTO) policyTO).getResources().addAll(((AccountPolicy) policy).getResourceNames());
+                break;
+
+            case GLOBAL_SYNC:
+            case SYNC:
+            default:
+                policyTO = (T) new SyncPolicyTO(policy.getType().isGlobal());
+                ((SyncPolicyTO) policyTO).setSpecification(policy.getSpecification(SyncPolicySpec.class));
+        }
+
+        policyTO.setId(policy.getKey());
+        policyTO.setDescription(policy.getDescription());
+
+        for (ExternalResource resource : resourceDAO.findByPolicy(policy)) {
+            policyTO.getUsedByResources().add(resource.getKey());
+        }
+        if (policy.getType().isGlobal()) {
+            for (ExternalResource resource : resourceDAO.findWithoutPolicy(policy.getType())) {
+                policyTO.getUsedByResources().add(resource.getKey());
+            }
+        }
+        for (Role role : roleDAO.findByPolicy(policy)) {
+            policyTO.getUsedByRoles().add(role.getKey());
+        }
+        if (policy.getType().isGlobal()) {
+            for (Role role : roleDAO.findWithoutPolicy(policy.getType())) {
+                policyTO.getUsedByRoles().add(role.getKey());
+            }
+        }
+
+        return policyTO;
+    }
+
+    private ExternalResource getResource(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            LOG.debug("Ignoring invalid resource {} ", resourceName);
+        }
+
+        return resource;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Policy> T getPolicy(T policy, final AbstractPolicyTO policyTO) {
+        if (policy != null && policy.getType() != policyTO.getType()) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+            sce.getElements().add(String.format("Cannot update %s from %s", policy.getType(), policyTO.getType()));
+            throw sce;
+        }
+
+        switch (policyTO.getType()) {
+            case GLOBAL_PASSWORD:
+            case PASSWORD:
+                if (!(policyTO instanceof PasswordPolicyTO)) {
+                    throw new ClassCastException("Expected " + PasswordPolicyTO.class.getName()
+                            + ", found " + policyTO.getClass().getName());
+                }
+                if (policy == null) {
+                    policy = (T) entityFactory.newPolicy(PasswordPolicy.class, policyTO.getType().isGlobal());
+                }
+                policy.setSpecification(((PasswordPolicyTO) policyTO).getSpecification());
+                break;
+
+            case GLOBAL_ACCOUNT:
+            case ACCOUNT:
+                if (!(policyTO instanceof AccountPolicyTO)) {
+                    throw new ClassCastException("Expected " + AccountPolicyTO.class.getName()
+                            + ", found " + policyTO.getClass().getName());
+                }
+                if (policy == null) {
+                    policy = (T) entityFactory.newPolicy(AccountPolicy.class, policyTO.getType().isGlobal());
+                }
+                policy.setSpecification(((AccountPolicyTO) policyTO).getSpecification());
+
+                if (((AccountPolicy) policy).getResources() != null
+                        && !((AccountPolicy) policy).getResources().isEmpty()) {
+                    ((AccountPolicy) policy).getResources().clear();
+                }
+                for (String resourceName : ((AccountPolicyTO) policyTO).getResources()) {
+                    ExternalResource resource = getResource(resourceName);
+
+                    if (resource != null) {
+                        ((AccountPolicy) policy).addResource(resource);
+                    }
+                }
+                break;
+
+            case GLOBAL_SYNC:
+            case SYNC:
+            default:
+                if (!(policyTO instanceof SyncPolicyTO)) {
+                    throw new ClassCastException("Expected " + SyncPolicyTO.class.getName()
+                            + ", found " + policyTO.getClass().getName());
+                }
+                if (policy == null) {
+                    policy = (T) entityFactory.newPolicy(SyncPolicy.class, policyTO.getType().isGlobal());
+                }
+                policy.setSpecification(((SyncPolicyTO) policyTO).getSpecification());
+        }
+
+        policy.setDescription(policyTO.getDescription());
+
+        return policy;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ReportDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ReportDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ReportDataBinder.java
new file mode 100644
index 0000000..952de94
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ReportDataBinder.java
@@ -0,0 +1,175 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.syncope.common.lib.report.AbstractReportletConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.to.ReportExecTO;
+import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.syncope.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.ReportExec;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.logic.report.Reportlet;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.logic.report.ReportletConfClass;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+
+@Component
+public class ReportDataBinder {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ReportDataBinder.class);
+
+    private static final String[] IGNORE_REPORT_PROPERTIES = { "key", "reportlets", "executions" };
+
+    private static final String[] IGNORE_REPORT_EXECUTION_PROPERTIES = { "key", "report", "execResult" };
+
+    @Autowired
+    private ReportExecDAO reportExecDAO;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public Set<Class<Reportlet>> getAllReportletClasses() {
+        Set<Class<Reportlet>> reportletClasses = new HashSet<Class<Reportlet>>();
+
+        for (String className : classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.REPORTLET)) {
+            try {
+                Class reportletClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
+                reportletClasses.add(reportletClass);
+            } catch (ClassNotFoundException e) {
+                LOG.warn("Could not load class {}", className);
+            } catch (LinkageError e) {
+                LOG.warn("Could not link class {}", className);
+            }
+        }
+        return reportletClasses;
+    }
+
+    public Class<? extends ReportletConf> getReportletConfClass(final Class<Reportlet> reportletClass) {
+        Class<? extends ReportletConf> result = null;
+
+        ReportletConfClass annotation = reportletClass.getAnnotation(ReportletConfClass.class);
+        if (annotation != null) {
+            result = annotation.value();
+        }
+
+        return result;
+    }
+
+    public Class<Reportlet> findReportletClassHavingConfClass(final Class<? extends ReportletConf> reportletConfClass) {
+        Class<Reportlet> result = null;
+        for (Class<Reportlet> reportletClass : getAllReportletClasses()) {
+            Class<? extends ReportletConf> found = getReportletConfClass(reportletClass);
+            if (found != null && found.equals(reportletConfClass)) {
+                result = reportletClass;
+            }
+        }
+
+        return result;
+    }
+
+    public void getReport(final Report report, final ReportTO reportTO) {
+        BeanUtils.copyProperties(reportTO, report, IGNORE_REPORT_PROPERTIES);
+        report.getReportletConfs().clear();
+        for (ReportletConf conf : reportTO.getReportletConfs()) {
+            report.addReportletConf(conf);
+        }
+    }
+
+    public ReportTO getReportTO(final Report report) {
+        ReportTO reportTO = new ReportTO();
+        reportTO.setId(report.getKey());
+        BeanUtils.copyProperties(report, reportTO, IGNORE_REPORT_PROPERTIES);
+
+        copyReportletConfs(report, reportTO);
+
+        ReportExec latestExec = reportExecDAO.findLatestStarted(report);
+        reportTO.setLatestExecStatus(latestExec == null
+                ? ""
+                : latestExec.getStatus());
+
+        reportTO.setStartDate(latestExec == null
+                ? null
+                : latestExec.getStartDate());
+
+        reportTO.setEndDate(latestExec == null
+                ? null
+                : latestExec.getEndDate());
+
+        for (ReportExec reportExec : report.getExecs()) {
+            reportTO.getExecutions().add(getReportExecTO(reportExec));
+        }
+
+        String triggerName = JobInstanceLoader.getTriggerName(JobInstanceLoader.getJobName(report));
+
+        Trigger trigger;
+        try {
+            trigger = scheduler.getScheduler().getTrigger(new TriggerKey(triggerName, Scheduler.DEFAULT_GROUP));
+        } catch (SchedulerException e) {
+            LOG.warn("While trying to get to " + triggerName, e);
+            trigger = null;
+        }
+
+        if (trigger != null) {
+            reportTO.setLastExec(trigger.getPreviousFireTime());
+            reportTO.setNextExec(trigger.getNextFireTime());
+        }
+
+        return reportTO;
+    }
+
+    private void copyReportletConfs(final Report report, final ReportTO reportTO) {
+        reportTO.getReportletConfs().clear();
+        for (ReportletConf reportletConf : report.getReportletConfs()) {
+            reportTO.getReportletConfs().add((AbstractReportletConf) reportletConf);
+        }
+    }
+
+    public ReportExecTO getReportExecTO(final ReportExec execution) {
+        ReportExecTO executionTO = new ReportExecTO();
+        executionTO.setKey(execution.getKey());
+        BeanUtils.copyProperties(execution, executionTO, IGNORE_REPORT_EXECUTION_PROPERTIES);
+        if (execution.getKey() != null) {
+            executionTO.setKey(execution.getKey());
+        }
+        executionTO.setReport(execution.getReport().getKey());
+
+        return executionTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ResourceDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ResourceDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ResourceDataBinder.java
new file mode 100644
index 0000000..b307eee
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/ResourceDataBinder.java
@@ -0,0 +1,389 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+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.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.persistence.api.dao.ConnInstanceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.Mapping;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.persistence.api.entity.role.RMapping;
+import org.apache.syncope.persistence.api.entity.role.RMappingItem;
+import org.apache.syncope.persistence.api.entity.user.UMapping;
+import org.apache.syncope.persistence.api.entity.user.UMappingItem;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ResourceDataBinder {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ResourceDataBinder.class);
+
+    private static final String[] MAPPINGITEM_IGNORE_PROPERTIES = { "key", "mapping" };
+
+    @Autowired
+    private ConnInstanceDAO connInstanceDAO;
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    public ExternalResource create(final ResourceTO resourceTO) {
+        return update(entityFactory.newEntity(ExternalResource.class), resourceTO);
+    }
+
+    public ExternalResource update(final ExternalResource resource, final ResourceTO resourceTO) {
+        if (resourceTO == null) {
+            return null;
+        }
+
+        resource.setKey(resourceTO.getKey());
+
+        if (resourceTO.getConnectorId() != null) {
+            ConnInstance connector = connInstanceDAO.find(resourceTO.getConnectorId());
+            resource.setConnector(connector);
+
+            if (!connector.getResources().contains(resource)) {
+                connector.addResource(resource);
+            }
+        }
+
+        resource.setEnforceMandatoryCondition(resourceTO.isEnforceMandatoryCondition());
+
+        resource.setPropagationPrimary(resourceTO.isPropagationPrimary());
+
+        resource.setPropagationPriority(resourceTO.getPropagationPriority());
+
+        resource.setRandomPwdIfNotProvided(resourceTO.isRandomPwdIfNotProvided());
+
+        resource.setPropagationMode(resourceTO.getPropagationMode());
+
+        if (resourceTO.getUmapping() == null || resourceTO.getUmapping().getItems().isEmpty()) {
+            resource.setUmapping(null);
+        } else {
+            UMapping mapping = entityFactory.newEntity(UMapping.class);
+            mapping.setResource(resource);
+            resource.setUmapping(mapping);
+            populateMapping(resourceTO.getUmapping(), mapping, entityFactory.newEntity(UMappingItem.class));
+        }
+        if (resourceTO.getRmapping() == null || resourceTO.getRmapping().getItems().isEmpty()) {
+            resource.setRmapping(null);
+        } else {
+            RMapping mapping = entityFactory.newEntity(RMapping.class);
+            mapping.setResource(resource);
+            resource.setRmapping(mapping);
+            populateMapping(resourceTO.getRmapping(), mapping, entityFactory.newEntity(RMappingItem.class));
+        }
+
+        resource.setCreateTraceLevel(resourceTO.getCreateTraceLevel());
+        resource.setUpdateTraceLevel(resourceTO.getUpdateTraceLevel());
+        resource.setDeleteTraceLevel(resourceTO.getDeleteTraceLevel());
+        resource.setSyncTraceLevel(resourceTO.getSyncTraceLevel());
+
+        resource.setPasswordPolicy(resourceTO.getPasswordPolicy() == null
+                ? null : (PasswordPolicy) policyDAO.find(resourceTO.getPasswordPolicy()));
+
+        resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
+                ? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
+
+        resource.setSyncPolicy(resourceTO.getSyncPolicy() == null
+                ? null : (SyncPolicy) policyDAO.find(resourceTO.getSyncPolicy()));
+
+        resource.setConnInstanceConfiguration(new HashSet<>(resourceTO.getConnConfProperties()));
+
+        if (resourceTO.getUsyncToken() == null) {
+            resource.setUsyncToken(null);
+        }
+        if (resourceTO.getRsyncToken() == null) {
+            resource.setRsyncToken(null);
+        }
+
+        resource.getPropagationActionsClassNames().clear();
+        resource.getPropagationActionsClassNames().addAll(resourceTO.getPropagationActionsClassNames());
+
+        return resource;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private void populateMapping(final MappingTO mappingTO, final Mapping mapping, final MappingItem prototype) {
+        mapping.setAccountLink(mappingTO.getAccountLink());
+
+        for (MappingItem item : getMappingItems(mappingTO.getItems(), prototype)) {
+            item.setMapping(mapping);
+            if (item.isAccountid()) {
+                mapping.setAccountIdItem(item);
+            } else if (item.isPassword()) {
+                ((UMapping) mapping).setPasswordItem((UMappingItem) item);
+            } else {
+                mapping.addItem(item);
+            }
+        }
+    }
+
+    private Set<MappingItem> getMappingItems(final Collection<MappingItemTO> itemTOs, final MappingItem prototype) {
+        Set<MappingItem> items = new HashSet<>(itemTOs.size());
+        for (MappingItemTO itemTO : itemTOs) {
+            items.add(getMappingItem(itemTO, prototype));
+        }
+
+        return items;
+    }
+
+    private MappingItem getMappingItem(final MappingItemTO itemTO, final MappingItem prototype) {
+        if (itemTO == null || itemTO.getIntMappingType() == null) {
+            LOG.error("Null mappingTO provided");
+            return null;
+        }
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        SyncopeClientException requiredValuesMissing = SyncopeClientException.build(
+                ClientExceptionType.RequiredValuesMissing);
+
+        if (itemTO.getIntAttrName() == null) {
+            if (IntMappingType.getEmbedded().contains(itemTO.getIntMappingType())) {
+                itemTO.setIntAttrName(itemTO.getIntMappingType().toString());
+            } else {
+                requiredValuesMissing.getElements().add("intAttrName");
+            }
+        }
+
+        // Throw composite exception if there is at least one element set
+        // in the composing exceptions
+        if (!requiredValuesMissing.isEmpty()) {
+            scce.addException(requiredValuesMissing);
+        }
+
+        // no mandatory condition implies mandatory condition false
+        if (!JexlUtil.isExpressionValid(itemTO.getMandatoryCondition() == null
+                ? "false" : itemTO.getMandatoryCondition())) {
+
+            SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
+                    ClientExceptionType.InvalidValues);
+
+            invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
+
+            scce.addException(invalidMandatoryCondition);
+        }
+
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+
+        MappingItem item = SerializationUtils.clone(prototype);
+        BeanUtils.copyProperties(itemTO, item, MAPPINGITEM_IGNORE_PROPERTIES);
+        return item;
+    }
+
+    public ConnInstance getConnInstance(final ExternalResource resource) {
+        final ConnInstance connInstanceClone = SerializationUtils.clone(resource.getConnector());
+        return getConnInstance(connInstanceClone, resource.getConnInstanceConfiguration());
+    }
+
+    public ConnInstance getConnInstance(final ResourceTO resourceTO) {
+        ConnInstance connInstance = connInstanceDAO.find(resourceTO.getConnectorId());
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + resourceTO.getConnectorId() + "'");
+        }
+
+        final ConnInstance connInstanceClone = SerializationUtils.clone(connInstance);
+        return getConnInstance(connInstanceClone, resourceTO.getConnConfProperties());
+    }
+
+    private ConnInstance getConnInstance(final ConnInstance connInstance, final Set<ConnConfProperty> overridden) {
+        final Set<ConnConfProperty> configuration = new HashSet<>();
+        final Map<String, ConnConfProperty> overridable = new HashMap<>();
+
+        // add not overridable properties
+        for (ConnConfProperty prop : connInstance.getConfiguration()) {
+            if (prop.isOverridable()) {
+                overridable.put(prop.getSchema().getName(), prop);
+            } else {
+                configuration.add(prop);
+            }
+        }
+
+        // add overridden properties
+        for (ConnConfProperty prop : overridden) {
+            if (overridable.containsKey(prop.getSchema().getName()) && !prop.getValues().isEmpty()) {
+                configuration.add(prop);
+                overridable.remove(prop.getSchema().getName());
+            }
+        }
+
+        // add overridable properties not overridden
+        configuration.addAll(overridable.values());
+
+        connInstance.setConfiguration(configuration);
+
+        return connInstance;
+    }
+
+    public List<ResourceTO> getResourceTOs(final Collection<? extends ExternalResource> resources) {
+        List<ResourceTO> resourceTOs = new ArrayList<>();
+        for (ExternalResource resource : resources) {
+            resourceTOs.add(getResourceTO(resource));
+        }
+
+        return resourceTOs;
+    }
+
+    public ResourceTO getResourceTO(final ExternalResource resource) {
+        if (resource == null) {
+            return null;
+        }
+
+        ResourceTO resourceTO = new ResourceTO();
+
+        // set sys info
+        resourceTO.setCreator(resource.getCreator());
+        resourceTO.setCreationDate(resource.getCreationDate());
+        resourceTO.setLastModifier(resource.getLastModifier());
+        resourceTO.setLastChangeDate(resource.getLastChangeDate());
+
+        // set the resource name
+        resourceTO.setKey(resource.getKey());
+
+        // set the connector instance
+        ConnInstance connector = resource.getConnector();
+
+        resourceTO.setConnectorId(connector == null ? null : connector.getKey());
+        resourceTO.setConnectorDisplayName(connector == null ? null : connector.getDisplayName());
+
+        // set the mappings
+        if (resource.getUmapping() != null) {
+            MappingTO mappingTO = new MappingTO();
+            resourceTO.setUmapping(mappingTO);
+            populateMappingTO(resource.getUmapping(), mappingTO);
+        }
+        if (resource.getRmapping() != null) {
+            MappingTO mappingTO = new MappingTO();
+            resourceTO.setRmapping(mappingTO);
+            populateMappingTO(resource.getRmapping(), mappingTO);
+        }
+
+        resourceTO.setEnforceMandatoryCondition(resource.isEnforceMandatoryCondition());
+
+        resourceTO.setPropagationPrimary(resource.isPropagationPrimary());
+
+        resourceTO.setPropagationPriority(resource.getPropagationPriority());
+
+        resourceTO.setRandomPwdIfNotProvided(resource.isRandomPwdIfNotProvided());
+
+        resourceTO.setPropagationMode(resource.getPropagationMode());
+
+        resourceTO.setCreateTraceLevel(resource.getCreateTraceLevel());
+        resourceTO.setUpdateTraceLevel(resource.getUpdateTraceLevel());
+        resourceTO.setDeleteTraceLevel(resource.getDeleteTraceLevel());
+        resourceTO.setSyncTraceLevel(resource.getSyncTraceLevel());
+
+        resourceTO.setPasswordPolicy(resource.getPasswordPolicy() == null
+                ? null : resource.getPasswordPolicy().getKey());
+
+        resourceTO.setAccountPolicy(resource.getAccountPolicy() == null
+                ? null : resource.getAccountPolicy().getKey());
+
+        resourceTO.setSyncPolicy(resource.getSyncPolicy() == null
+                ? null : resource.getSyncPolicy().getKey());
+
+        resourceTO.getConnConfProperties().addAll(resource.getConnInstanceConfiguration());
+
+        resourceTO.setUsyncToken(resource.getSerializedUSyncToken());
+        resourceTO.setRsyncToken(resource.getSerializedRSyncToken());
+
+        resourceTO.getPropagationActionsClassNames().addAll(resource.getPropagationActionsClassNames());
+
+        return resourceTO;
+    }
+
+    private void populateMappingTO(final Mapping<?> mapping, final MappingTO mappingTO) {
+        mappingTO.setAccountLink(mapping.getAccountLink());
+
+        for (MappingItemTO itemTO : getMappingItemTOs(mapping.getItems())) {
+            if (itemTO.isAccountid()) {
+                mappingTO.setAccountIdItem(itemTO);
+            } else if (itemTO.isPassword()) {
+                mappingTO.setPasswordItem(itemTO);
+            } else {
+                mappingTO.addItem(itemTO);
+            }
+        }
+    }
+
+    private Set<MappingItemTO> getMappingItemTOs(final Collection<? extends MappingItem> items) {
+        Set<MappingItemTO> mappingTOs = new HashSet<>();
+        for (MappingItem item : items) {
+            LOG.debug("Asking for TO for {}", item);
+            mappingTOs.add(getMappingItemTO(item));
+        }
+
+        LOG.debug("Collected TOs: {}", mappingTOs);
+
+        return mappingTOs;
+    }
+
+    private MappingItemTO getMappingItemTO(final MappingItem item) {
+        if (item == null) {
+            LOG.error("Provided null mapping");
+
+            return null;
+        }
+
+        MappingItemTO itemTO = new MappingItemTO();
+
+        BeanUtils.copyProperties(item, itemTO, MAPPINGITEM_IGNORE_PROPERTIES);
+
+        itemTO.setKey(item.getKey());
+
+        LOG.debug("Obtained SchemaMappingTO {}", itemTO);
+
+        return itemTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/RoleDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/RoleDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/RoleDataBinder.java
new file mode 100644
index 0000000..d8dc0df
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/RoleDataBinder.java
@@ -0,0 +1,429 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.AttrTemplate;
+import org.apache.syncope.persistence.api.entity.Entitlement;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.Schema;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MDerSchema;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MPlainSchema;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MVirSchema;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RDerSchema;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainSchema;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RVirSchema;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+import org.apache.syncope.server.utils.ConnObjectUtil;
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional(rollbackFor = { Throwable.class })
+public class RoleDataBinder extends AbstractAttributableDataBinder {
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Transactional(readOnly = true)
+    public List<WorkflowResult<Long>> getUsersOnResourcesOnlyBecauseOfRole(final Long roleId) {
+        Role role = roleDAO.authFetchRole(roleId);
+
+        List<WorkflowResult<Long>> result = new ArrayList<WorkflowResult<Long>>();
+
+        for (Membership membership : roleDAO.findMemberships(role)) {
+            User user = membership.getUser();
+
+            PropagationByResource propByRes = new PropagationByResource();
+            for (ExternalResource resource : role.getResources()) {
+                if (!user.getOwnResources().contains(resource)) {
+                    propByRes.add(ResourceOperation.DELETE, resource.getKey());
+                }
+
+                if (!propByRes.isEmpty()) {
+                    result.add(new WorkflowResult<Long>(user.getKey(), propByRes, Collections.<String>emptySet()));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private <T extends AttrTemplate<S>, S extends Schema> void setAttrTemplates(
+            final Role role, final List<String> schemaNames,
+            final Class<T> templateClass, final Class<S> schemaClass) {
+
+        List<T> toRemove = new ArrayList<T>();
+        for (T template : role.getAttrTemplates(templateClass)) {
+            if (!schemaNames.contains(template.getSchema().getKey())) {
+                toRemove.add(template);
+            }
+        }
+        role.getAttrTemplates(templateClass).removeAll(toRemove);
+
+        for (String schemaName : schemaNames) {
+            if (role.getAttrTemplate(templateClass, schemaName) == null) {
+                S schema = getSchema(schemaName, schemaClass);
+                if (schema != null) {
+                    try {
+                        T template = templateClass.newInstance();
+                        template.setSchema(schema);
+                        template.setOwner(role);
+                        role.getAttrTemplates(templateClass).add(template);
+                    } catch (Exception e) {
+                        LOG.error("Could not create template for {}", templateClass, e);
+                    }
+                }
+            }
+        }
+    }
+
+    public Role create(final Role role, final RoleTO roleTO) {
+        role.setInheritOwner(roleTO.isInheritOwner());
+
+        role.setInheritPlainAttrs(roleTO.isInheritAttrs());
+        role.setInheritDerAttrs(roleTO.isInheritDerAttrs());
+        role.setInheritVirAttrs(roleTO.isInheritVirAttrs());
+
+        role.setInheritTemplates(roleTO.isInheritTemplates());
+
+        role.setInheritPasswordPolicy(roleTO.isInheritPasswordPolicy());
+        role.setInheritAccountPolicy(roleTO.isInheritAccountPolicy());
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        // name and parent
+        SyncopeClientException invalidRoles = SyncopeClientException.build(ClientExceptionType.InvalidRoles);
+        if (roleTO.getName() == null) {
+            LOG.error("No name specified for this role");
+
+            invalidRoles.getElements().add("No name specified for this role");
+        } else {
+            role.setName(roleTO.getName());
+        }
+        Long parentRoleKey = null;
+        if (roleTO.getParent() != 0) {
+            Role parentRole = roleDAO.find(roleTO.getParent());
+            if (parentRole == null) {
+                LOG.error("Could not find role with id " + roleTO.getParent());
+
+                invalidRoles.getElements().add(String.valueOf(roleTO.getParent()));
+                scce.addException(invalidRoles);
+            } else {
+                role.setParent(parentRole);
+                parentRoleKey = role.getParent().getKey();
+            }
+        }
+
+        Role otherRole = roleDAO.find(roleTO.getName(), parentRoleKey);
+        if (otherRole != null) {
+            LOG.error("Another role exists with the same name " + "and the same parent role: " + otherRole);
+
+            invalidRoles.getElements().add(roleTO.getName());
+        }
+
+        // attribute templates
+        setAttrTemplates(role, roleTO.getRAttrTemplates(), RPlainAttrTemplate.class, RPlainSchema.class);
+        setAttrTemplates(role, roleTO.getRDerAttrTemplates(), RDerAttrTemplate.class, RDerSchema.class);
+        setAttrTemplates(role, roleTO.getRVirAttrTemplates(), RVirAttrTemplate.class, RVirSchema.class);
+        setAttrTemplates(role, roleTO.getMAttrTemplates(), MPlainAttrTemplate.class, MPlainSchema.class);
+        setAttrTemplates(role, roleTO.getMDerAttrTemplates(), MDerAttrTemplate.class, MDerSchema.class);
+        setAttrTemplates(role, roleTO.getMVirAttrTemplates(), MVirAttrTemplate.class, MVirSchema.class);
+
+        // attributes, derived attributes, virtual attributes and resources
+        fill(role, roleTO, attrUtilFactory.getInstance(AttributableType.ROLE), scce);
+
+        // entitlements
+        for (String entitlementName : roleTO.getEntitlements()) {
+            Entitlement entitlement = entitlementDAO.find(entitlementName);
+            if (entitlement == null) {
+                LOG.warn("Ignoring invalid entitlement {}", entitlementName);
+            } else {
+                role.addEntitlement(entitlement);
+            }
+        }
+
+        // owner
+        if (roleTO.getUserOwner() != null) {
+            User owner = userDAO.find(roleTO.getUserOwner());
+            if (owner == null) {
+                LOG.warn("Ignoring invalid user specified as owner: {}", roleTO.getUserOwner());
+            } else {
+                role.setUserOwner(owner);
+            }
+        }
+        if (roleTO.getRoleOwner() != null) {
+            Role owner = roleDAO.find(roleTO.getRoleOwner());
+            if (owner == null) {
+                LOG.warn("Ignoring invalid role specified as owner: {}", roleTO.getRoleOwner());
+            } else {
+                role.setRoleOwner(owner);
+            }
+        }
+
+        // policies
+        if (roleTO.getPasswordPolicy() != null) {
+            role.setPasswordPolicy((PasswordPolicy) policyDAO.find(roleTO.getPasswordPolicy()));
+        }
+        if (roleTO.getAccountPolicy() != null) {
+            role.setAccountPolicy((AccountPolicy) policyDAO.find(roleTO.getAccountPolicy()));
+        }
+
+        return role;
+    }
+
+    public PropagationByResource update(final Role role, final RoleMod roleMod) {
+        PropagationByResource propByRes = new PropagationByResource();
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        Set<String> currentResources = role.getResourceNames();
+
+        // name
+        SyncopeClientException invalidRoles = SyncopeClientException.build(ClientExceptionType.InvalidRoles);
+        if (roleMod.getName() != null) {
+            Role otherRole = roleDAO.find(roleMod.getName(),
+                    role.getParent() == null ? null : role.getParent().getKey());
+            if (otherRole == null || role.equals(otherRole)) {
+                if (!roleMod.getName().equals(role.getName())) {
+                    propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+                    for (String resource : currentResources) {
+                        propByRes.addOldAccountId(resource, role.getName());
+                    }
+
+                    role.setName(roleMod.getName());
+                }
+            } else {
+                LOG.error("Another role exists with the same name and the same parent role: " + otherRole);
+
+                invalidRoles.getElements().add(roleMod.getName());
+                scce.addException(invalidRoles);
+            }
+        }
+
+        if (roleMod.getInheritOwner() != null) {
+            role.setInheritOwner(roleMod.getInheritOwner());
+        }
+
+        if (roleMod.getInheritTemplates() != null) {
+            role.setInheritTemplates(roleMod.getInheritTemplates());
+        }
+
+        if (roleMod.getInheritPlainAttrs() != null) {
+            role.setInheritPlainAttrs(roleMod.getInheritPlainAttrs());
+        }
+        if (roleMod.getInheritDerAttrs() != null) {
+            role.setInheritDerAttrs(roleMod.getInheritDerAttrs());
+        }
+        if (roleMod.getInheritVirAttrs() != null) {
+            role.setInheritVirAttrs(roleMod.getInheritVirAttrs());
+        }
+
+        if (roleMod.getInheritPasswordPolicy() != null) {
+            role.setInheritPasswordPolicy(roleMod.getInheritPasswordPolicy());
+        }
+        if (roleMod.getInheritAccountPolicy() != null) {
+            role.setInheritAccountPolicy(roleMod.getInheritAccountPolicy());
+        }
+
+        // entitlements
+        if (roleMod.isModEntitlements()) {
+            role.getEntitlements().clear();
+            for (String entitlementName : roleMod.getEntitlements()) {
+                Entitlement entitlement = entitlementDAO.find(entitlementName);
+                if (entitlement == null) {
+                    LOG.warn("Ignoring invalid entitlement {}", entitlementName);
+                } else {
+                    role.addEntitlement(entitlement);
+                }
+            }
+        }
+
+        // attribute templates
+        if (roleMod.isModRAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getRPlainAttrTemplates(), RPlainAttrTemplate.class, RPlainSchema.class);
+        }
+        if (roleMod.isModRDerAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getRDerAttrTemplates(), RDerAttrTemplate.class, RDerSchema.class);
+        }
+        if (roleMod.isModRVirAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getRVirAttrTemplates(), RVirAttrTemplate.class, RVirSchema.class);
+        }
+        if (roleMod.isModMAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getMPlainAttrTemplates(), MPlainAttrTemplate.class, MPlainSchema.class);
+        }
+        if (roleMod.isModMDerAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getMDerAttrTemplates(), MDerAttrTemplate.class, MDerSchema.class);
+        }
+        if (roleMod.isModMVirAttrTemplates()) {
+            setAttrTemplates(role, roleMod.getMVirAttrTemplates(), MVirAttrTemplate.class, MVirSchema.class);
+        }
+
+        // policies
+        if (roleMod.getPasswordPolicy() != null) {
+            role.setPasswordPolicy(roleMod.getPasswordPolicy().getKey() == null
+                    ? null
+                    : (PasswordPolicy) policyDAO.find(roleMod.getPasswordPolicy().getKey()));
+        }
+        if (roleMod.getAccountPolicy() != null) {
+            role.setAccountPolicy(roleMod.getAccountPolicy().getKey() == null
+                    ? null
+                    : (AccountPolicy) policyDAO.find(roleMod.getAccountPolicy().getKey()));
+        }
+
+        // owner
+        if (roleMod.getUserOwner() != null) {
+            role.setUserOwner(roleMod.getUserOwner().getKey() == null
+                    ? null
+                    : userDAO.find(roleMod.getUserOwner().getKey()));
+        }
+        if (roleMod.getRoleOwner() != null) {
+            role.setRoleOwner(roleMod.getRoleOwner().getKey() == null
+                    ? null
+                    : roleDAO.find(roleMod.getRoleOwner().getKey()));
+        }
+
+        // attributes, derived attributes, virtual attributes and resources
+        propByRes.merge(fill(role, roleMod, attrUtilFactory.getInstance(AttributableType.ROLE), scce));
+
+        return propByRes;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Transactional(readOnly = true)
+    public RoleTO getRoleTO(final Role role) {
+        connObjectUtil.retrieveVirAttrValues(role, attrUtilFactory.getInstance(AttributableType.ROLE));
+
+        RoleTO roleTO = new RoleTO();
+
+        // set sys info
+        roleTO.setCreator(role.getCreator());
+        roleTO.setCreationDate(role.getCreationDate());
+        roleTO.setLastModifier(role.getLastModifier());
+        roleTO.setLastChangeDate(role.getLastChangeDate());
+
+        roleTO.setKey(role.getKey());
+        roleTO.setName(role.getName());
+
+        roleTO.setInheritOwner(role.isInheritOwner());
+
+        roleTO.setInheritTemplates(role.isInheritTemplates());
+
+        roleTO.setInheritAttrs(role.isInheritPlainAttrs());
+        roleTO.setInheritDerAttrs(role.isInheritDerAttrs());
+        roleTO.setInheritVirAttrs(role.isInheritVirAttrs());
+
+        roleTO.setInheritPasswordPolicy(role.isInheritPasswordPolicy());
+        roleTO.setInheritAccountPolicy(role.isInheritAccountPolicy());
+
+        if (role.getParent() != null) {
+            roleTO.setParent(role.getParent().getKey());
+        }
+
+        if (role.getUserOwner() != null) {
+            roleTO.setUserOwner(role.getUserOwner().getKey());
+        }
+        if (role.getRoleOwner() != null) {
+            roleTO.setRoleOwner(role.getRoleOwner().getKey());
+        }
+
+        // -------------------------
+        // Retrieve all [derived/virtual] attributes (inherited and not)
+        // -------------------------        
+        final List<? extends RPlainAttr> allAttributes = role.findLastInheritedAncestorPlainAttrs();
+
+        final List<? extends RDerAttr> allDerAttributes = role.findLastInheritedAncestorDerAttrs();
+
+        final List<? extends RVirAttr> allVirAttributes = role.findLastInheritedAncestorVirAttrs();
+        // -------------------------
+
+        fillTO(roleTO, allAttributes, allDerAttributes, allVirAttributes, role.getResources());
+
+        for (Entitlement entitlement : role.getEntitlements()) {
+            roleTO.getEntitlements().add(entitlement.getKey());
+        }
+
+        for (RPlainAttrTemplate template : role.findInheritedTemplates(RPlainAttrTemplate.class)) {
+            roleTO.getRAttrTemplates().add(template.getSchema().getKey());
+        }
+        for (RDerAttrTemplate template : role.findInheritedTemplates(RDerAttrTemplate.class)) {
+            roleTO.getRDerAttrTemplates().add(template.getSchema().getKey());
+        }
+        for (RVirAttrTemplate template : role.findInheritedTemplates(RVirAttrTemplate.class)) {
+            roleTO.getRVirAttrTemplates().add(template.getSchema().getKey());
+        }
+        for (MPlainAttrTemplate template : role.findInheritedTemplates(MPlainAttrTemplate.class)) {
+            roleTO.getMAttrTemplates().add(template.getSchema().getKey());
+        }
+        for (MDerAttrTemplate template : role.findInheritedTemplates(MDerAttrTemplate.class)) {
+            roleTO.getMDerAttrTemplates().add(template.getSchema().getKey());
+        }
+        for (MVirAttrTemplate template : role.findInheritedTemplates(MVirAttrTemplate.class)) {
+            roleTO.getMVirAttrTemplates().add(template.getSchema().getKey());
+        }
+
+        roleTO.setPasswordPolicy(role.getPasswordPolicy() == null
+                ? null
+                : role.getPasswordPolicy().getKey());
+        roleTO.setAccountPolicy(role.getAccountPolicy() == null
+                ? null
+                : role.getAccountPolicy().getKey());
+
+        return roleTO;
+    }
+
+    @Transactional(readOnly = true)
+    public RoleTO getRoleTO(final Long key) {
+        return getRoleTO(roleDAO.authFetchRole(key));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SchemaDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SchemaDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SchemaDataBinder.java
new file mode 100644
index 0000000..79cb4a5
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SchemaDataBinder.java
@@ -0,0 +1,163 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SchemaDataBinder {
+
+    @Autowired
+    private PlainSchemaDAO schemaDAO;
+
+    // --------------- PLAIN -----------------
+    private <T extends PlainSchema> void fill(final T schema, final PlainSchemaTO schemaTO) {
+        if (!JexlUtil.isExpressionValid(schemaTO.getMandatoryCondition())) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+            sce.getElements().add(schemaTO.getMandatoryCondition());
+            throw sce;
+        }
+
+        BeanUtils.copyProperties(schemaTO, schema);
+    }
+
+    public <T extends PlainSchema> void create(final PlainSchemaTO schemaTO, final T schema) {
+        fill(schema, schemaTO);
+    }
+
+    public <T extends PlainSchema> void update(final PlainSchemaTO schemaTO, final T schema,
+            final AttributableUtil attributableUtil) {
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        List<PlainAttr> attrs = schemaDAO.findAttrs(schema, attributableUtil.plainAttrClass());
+        if (!attrs.isEmpty()) {
+            if (schema.getType() != schemaTO.getType()) {
+                SyncopeClientException e = SyncopeClientException.build(
+                        ClientExceptionType.valueOf("Invalid" + schema.getClass().getSimpleName()));
+                e.getElements().add("Cannot change type since " + schema.getKey() + " has attributes");
+
+                scce.addException(e);
+            }
+            if (schema.isUniqueConstraint() != schemaTO.isUniqueConstraint()) {
+                SyncopeClientException e = SyncopeClientException.build(ClientExceptionType.valueOf("Invalid"
+                        + schema.getClass().getSimpleName()));
+                e.getElements().add("Cannot alter unique contraint since " + schema.getKey() + " has attributes");
+
+                scce.addException(e);
+            }
+        }
+
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+
+        fill(schema, schemaTO);
+    }
+
+    public <T extends PlainSchema> PlainSchemaTO getSchemaTO(
+            final T schema, final AttributableUtil attributableUtil) {
+
+        PlainSchemaTO schemaTO = new PlainSchemaTO();
+        BeanUtils.copyProperties(schema, schemaTO);
+
+        return schemaTO;
+    }
+
+    // --------------- DERIVED -----------------
+    private <T extends DerSchema> T populate(final T derSchema, final DerSchemaTO derSchemaTO) {
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        if (StringUtils.isBlank(derSchemaTO.getExpression())) {
+            SyncopeClientException requiredValuesMissing =
+                    SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+            requiredValuesMissing.getElements().add("expression");
+
+            scce.addException(requiredValuesMissing);
+        } else if (!JexlUtil.isExpressionValid(derSchemaTO.getExpression())) {
+            SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
+                    ClientExceptionType.InvalidValues);
+            invalidMandatoryCondition.getElements().add(derSchemaTO.getExpression());
+
+            scce.addException(invalidMandatoryCondition);
+        }
+
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+
+        BeanUtils.copyProperties(derSchemaTO, derSchema);
+
+        return derSchema;
+    }
+
+    public <T extends DerSchema> T create(final DerSchemaTO derSchemaTO, final T derSchema) {
+        return populate(derSchema, derSchemaTO);
+    }
+
+    public <T extends DerSchema> T update(final DerSchemaTO derSchemaTO, final T derSchema) {
+        return populate(derSchema, derSchemaTO);
+    }
+
+    public <T extends DerSchema> DerSchemaTO getDerSchemaTO(final T derSchema) {
+        DerSchemaTO derSchemaTO = new DerSchemaTO();
+        BeanUtils.copyProperties(derSchema, derSchemaTO);
+
+        return derSchemaTO;
+    }
+
+    // --------------- VIRTUAL -----------------
+    private <T extends VirSchema> T fill(final T virSchema, final VirSchemaTO virSchemaTO) {
+        BeanUtils.copyProperties(virSchemaTO, virSchema);
+
+        return virSchema;
+    }
+
+    public <T extends VirSchema> T create(final VirSchemaTO virSchemaTO, final T virSchema) {
+        return fill(virSchema, virSchemaTO);
+    }
+
+    public <T extends VirSchema> T update(final VirSchemaTO virSchemaTO, final T virSchema) {
+        return fill(virSchema, virSchemaTO);
+    }
+
+    public <T extends VirSchema> VirSchemaTO getVirSchemaTO(final T virSchema) {
+        VirSchemaTO virSchemaTO = new VirSchemaTO();
+        BeanUtils.copyProperties(virSchema, virSchemaTO);
+
+        return virSchemaTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SecurityQuestionDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SecurityQuestionDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SecurityQuestionDataBinder.java
new file mode 100644
index 0000000..b8dce1d
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/SecurityQuestionDataBinder.java
@@ -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.
+ */
+package org.apache.syncope.server.logic.data;
+
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SecurityQuestionDataBinder {
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    public SecurityQuestionTO getSecurityQuestionTO(final SecurityQuestion securityQuestion) {
+        SecurityQuestionTO securityQuestionTO = new SecurityQuestionTO();
+
+        BeanUtils.copyProperties(securityQuestion, securityQuestionTO);
+
+        return securityQuestionTO;
+    }
+
+    public SecurityQuestion create(final SecurityQuestionTO securityQuestionTO) {
+        SecurityQuestion result = entityFactory.newEntity(SecurityQuestion.class);
+        update(result, securityQuestionTO);
+        return result;
+    }
+
+    public void update(final SecurityQuestion securityQuestion, final SecurityQuestionTO securityQuestionTO) {
+        BeanUtils.copyProperties(securityQuestionTO, securityQuestion, "key");
+    }
+}


[02/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
new file mode 100644
index 0000000..1b37718
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -0,0 +1,77 @@
+/*
+ * 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.provisioning.api.propagation;
+
+import java.util.Collection;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+
+/**
+ * Execute propagation tasks.
+ *
+ * @see PropagationTask
+ */
+public interface PropagationTaskExecutor {
+
+    /**
+     * Name for special propagation attribute used to indicate whether there are attributes, marked as mandatory in the
+     * mapping but not to be propagated.
+     */
+    String MANDATORY_MISSING_ATTR_NAME = "__MANDATORY_MISSING__";
+
+    /**
+     * Name for special propagation attribute used to indicate whether there are attributes, marked as mandatory in the
+     * mapping but about to be propagated as null or empty.
+     */
+    String MANDATORY_NULL_OR_EMPTY_ATTR_NAME = "__MANDATORY_NULL_OR_EMPTY__";
+
+    /**
+     * Execute the given PropagationTask and returns the generated TaskExec.
+     *
+     * @param task to be executed
+     * @return the generated TaskExec
+     */
+    TaskExec execute(PropagationTask task);
+
+    /**
+     * Execute the given PropagationTask, invoke the given handler and returns the generated TaskExec.
+     *
+     * @param task to be executed
+     * @param reporter to report propagation execution status
+     * @return the generated TaskExec
+     */
+    TaskExec execute(PropagationTask task, PropagationReporter reporter);
+
+    /**
+     * Execute a collection of PropagationTask objects.
+     * The process is interrupted as soon as the result of the communication with a primary resource is in error.
+     *
+     * @param tasks to be executed
+     */
+    void execute(Collection<PropagationTask> tasks);
+
+    /**
+     * Execute a collection of PropagationTask objects and invoke the given handler on each of these.
+     * The process is interrupted as soon as the result of the communication with a primary resource is in error.
+     *
+     * @param tasks to be execute, in given order
+     * @param reporter to report propagation execution status
+     */
+    void execute(Collection<PropagationTask> tasks, PropagationReporter reporter);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
new file mode 100644
index 0000000..4056e74
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/sync/SyncCorrelationRule.java
@@ -0,0 +1,36 @@
+/*
+ * 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.provisioning.api.sync;
+
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+
+/**
+ * Interface for correlation rule to be evaluated during SyncJob execution.
+ */
+public interface SyncCorrelationRule {
+
+    /**
+     * Return a search condition.
+     *
+     * @param connObj connector object.
+     * @return search condition.
+     */
+    SearchCond getSearchCond(ConnectorObject connObj);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/pom.xml b/syncope620/server/provisioning-common/pom.xml
new file mode 100644
index 0000000..d920258
--- /dev/null
+++ b/syncope620/server/provisioning-common/pom.xml
@@ -0,0 +1,44 @@
+<?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-server</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Server Provisioning Common</name>
+  <description>Apache Syncope Server Provisioning Common</description>
+  <groupId>org.apache.syncope.server</groupId>
+  <artifactId>syncope-provisioning-common</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-provisioning-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
new file mode 100644
index 0000000..3491075
--- /dev/null
+++ b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/DisabledVirAttrCache.java
@@ -0,0 +1,52 @@
+/*
+ * 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.provisioning.common.cache;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * Empty virtual attribute value cache implementation.
+ */
+public class DisabledVirAttrCache implements VirAttrCache {
+
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        // nothing to do
+    }
+
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return null;
+    }
+
+    @Override
+    public boolean isValidEntry(VirAttrCacheValue value) {
+        return false;
+    }
+
+    @Override
+    public void put(
+            final AttributableType type, final Long id, final String schemaName, final VirAttrCacheValue value) {
+
+        // nothing to do
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
new file mode 100644
index 0000000..0199499
--- /dev/null
+++ b/syncope620/server/provisioning-common/src/main/java/org/apache/syncope/provisioning/common/cache/MemoryVirAttrCache.java
@@ -0,0 +1,151 @@
+/*
+ * 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.provisioning.common.cache;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheKey;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * In-memory (HashMap) virtual attribute value cache implementation.
+ */
+public class MemoryVirAttrCache implements VirAttrCache {
+
+    /**
+     * Elapsed time in seconds.
+     */
+    protected int ttl;
+
+    /**
+     * Max cache size.
+     */
+    protected int maxCacheSize;
+
+    /**
+     * Cache entries.
+     */
+    protected final Map<VirAttrCacheKey, VirAttrCacheValue> cache = new HashMap<VirAttrCacheKey, VirAttrCacheValue>();
+
+    public MemoryVirAttrCache(final int ttl, final int maxCacheSize) {
+        this.ttl = ttl;
+        this.maxCacheSize = maxCacheSize;
+    }
+
+    /**
+     * Cache virtual attribute values.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute name
+     * @param value virtual attribute values
+     */
+    @Override
+    public void put(
+            final AttributableType type,
+            final Long id,
+            final String schemaName,
+            final VirAttrCacheValue value) {
+
+        synchronized (cache) {
+            // this operations (retrieve cache space and put entry on) have to be thread safe.
+            if (this.cache.size() >= this.maxCacheSize) {
+                free();
+            }
+
+            cache.put(new VirAttrCacheKey(type, id, schemaName), value);
+        }
+    }
+
+    /**
+     * Retrieve cached value. Return null in case of virtual attribute not cached.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name.
+     * @return cached values or null if virtual attribute is not cached.
+     */
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return cache.get(new VirAttrCacheKey(type, id, schemaName));
+    }
+
+    /**
+     * Force entry expiring.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name
+     */
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        final VirAttrCacheValue value = cache.get(new VirAttrCacheKey(type, id, schemaName));
+        if (isValidEntry(value)) {
+            synchronized (cache) {
+                value.forceExpiring();
+            }
+        }
+    }
+
+    /**
+     * Remove expired entries if exist. If required, one entry at least (the latest recently used) will be taken off.
+     * This method is not thread safe: the caller have to take care to synchronize the call.
+     */
+    private void free() {
+        final Set<VirAttrCacheKey> toBeRemoved = new HashSet<VirAttrCacheKey>();
+
+        Map.Entry<VirAttrCacheKey, VirAttrCacheValue> latest = null;
+
+        for (Map.Entry<VirAttrCacheKey, VirAttrCacheValue> entry : cache.entrySet()) {
+            if (isValidEntry(entry.getValue())) {
+                final Date date = entry.getValue().getLastAccessDate();
+                if (latest == null || latest.getValue().getLastAccessDate().after(date)) {
+                    latest = entry;
+                }
+            } else {
+                toBeRemoved.add(entry.getKey());
+            }
+        }
+
+        if (toBeRemoved.isEmpty() && latest != null) {
+            // remove the oldest entry
+            cache.remove(latest.getKey());
+        } else {
+            // remove expired entries
+            cache.keySet().removeAll(toBeRemoved);
+        }
+    }
+
+    /**
+     * Cache entry is valid if and only if value exist and it is not expired.
+     *
+     * @param value cache entry value.
+     * @return TRUE if the value is valid; FALSE otherwise.
+     */
+    @Override
+    public boolean isValidEntry(final VirAttrCacheValue value) {
+        final Date expiringDate = new Date(value == null ? 0 : value.getCreationDate().getTime() + ttl * 1000);
+        return expiringDate.after(new Date());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
deleted file mode 100644
index 3f2735a..0000000
--- a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/SecureRandomUtil.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.server.security;
-
-import java.security.SecureRandom;
-
-import org.apache.commons.lang3.RandomStringUtils;
-
-public class SecureRandomUtil {
-    
-    private static final SecureRandom RANDOM = new SecureRandom();
-
-    public static String generateRandomPassword(final int tokenLength) {
-        return RandomStringUtils.random(tokenLength, 0, 0, true, false, null, RANDOM);
-    }
-    
-    public static String generateRandomLetter() {
-        return RandomStringUtils.random(1, 0, 0, true, false, null, RANDOM);
-    }
-    
-    public static String generateRandomNumber() {
-        return RandomStringUtils.random(1, 0, 0, false, true, null, RANDOM);
-    }
-    
-    public static String generateRandomSpecialCharacter(char[] characters) {
-        return RandomStringUtils.random(1, 0, 0, false, false, characters, RANDOM);
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
new file mode 100644
index 0000000..6586ccc
--- /dev/null
+++ b/syncope620/server/security/src/main/java/org/apache/syncope/server/security/UnauthorizedRoleException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.server.security;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class UnauthorizedRoleException extends RuntimeException {
+
+    private static final long serialVersionUID = 7540587364235915081L;
+
+    private final Set<Long> roleIds;
+
+    public UnauthorizedRoleException(final Set<Long> roleIds) {
+        super("Missing entitlement for role(s) " + roleIds);
+        this.roleIds = roleIds;
+    }
+
+    public UnauthorizedRoleException(final Long roleId) {
+        this(Collections.singleton(roleId));
+    }
+
+    public Set<Long> getRoleIds() {
+        return roleIds;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/pom.xml b/syncope620/server/utils/pom.xml
index af4a55e..4e9fb6f 100644
--- a/syncope620/server/utils/pom.xml
+++ b/syncope620/server/utils/pom.xml
@@ -62,7 +62,7 @@ under the License.
 
     <dependency>
       <groupId>org.springframework</groupId>
-      <artifactId>spring-context</artifactId>
+      <artifactId>spring-tx</artifactId>
     </dependency>
 
     <dependency>
@@ -70,6 +70,21 @@ under the License.
       <artifactId>syncope-persistence-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-provisioning-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-server-security</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-server-spring</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
new file mode 100644
index 0000000..d2afd35
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ConnObjectUtil.java
@@ -0,0 +1,764 @@
+/*
+ * 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.server.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.AttributableOperations;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.Connector;
+import org.apache.syncope.provisioning.api.ConnectorFactory;
+import org.apache.syncope.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.provisioning.api.cache.VirAttrCacheValue;
+import org.apache.syncope.server.security.Encryptor;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import org.apache.syncope.server.spring.ApplicationContextProvider;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedByteArray;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ConnObjectUtil {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtil.class);
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private PasswordGenerator pwdGen;
+
+    private final Encryptor encryptor = Encryptor.getInstance();
+
+    /**
+     * Virtual attribute cache.
+     */
+    @Autowired
+    private VirAttrCache virAttrCache;
+
+    public ObjectClass fromSubject(final Subject<?, ?, ?> subject) {
+        if (subject == null) {
+            throw new IllegalArgumentException("No ObjectClass could be provided for " + subject);
+        }
+
+        ObjectClass result = null;
+        if (subject instanceof User) {
+            result = ObjectClass.ACCOUNT;
+        }
+        if (subject instanceof Role) {
+            result = ObjectClass.GROUP;
+        }
+
+        return result;
+    }
+
+    /**
+     * Build a UserTO / RoleTO out of connector object attributes and schema mapping.
+     *
+     * @param obj connector object
+     * @param syncTask synchronization task
+     * @param attrUtil AttributableUtil
+     * @param <T> user/role
+     * @return UserTO for the user to be created
+     */
+    @Transactional(readOnly = true)
+    public <T extends AbstractSubjectTO> T getSubjectTO(final ConnectorObject obj, final SyncTask syncTask,
+            final AttributableUtil attrUtil) {
+
+        T subjectTO = getSubjectTOFromConnObject(obj, syncTask, attrUtil);
+
+        // (for users) if password was not set above, generate
+        if (subjectTO instanceof UserTO && StringUtils.isBlank(((UserTO) subjectTO).getPassword())) {
+            final UserTO userTO = (UserTO) subjectTO;
+
+            List<PasswordPolicySpec> ppSpecs = new ArrayList<>();
+
+            PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy();
+            if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) {
+                ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class));
+            }
+
+            for (MembershipTO memb : userTO.getMemberships()) {
+                Role role = roleDAO.find(memb.getRoleId());
+                if (role != null && role.getPasswordPolicy() != null
+                        && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                    ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+                }
+            }
+
+            for (String resName : userTO.getResources()) {
+                ExternalResource resource = resourceDAO.find(resName);
+                if (resource != null && resource.getPasswordPolicy() != null
+                        && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                    ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+                }
+            }
+
+            String password;
+            try {
+                password = pwdGen.generate(ppSpecs);
+            } catch (InvalidPasswordPolicySpecException e) {
+                LOG.error("Could not generate policy-compliant random password for {}", userTO, e);
+
+                password = SecureRandomUtil.generateRandomPassword(16);
+            }
+            userTO.setPassword(password);
+        }
+
+        return subjectTO;
+    }
+
+    /**
+     * Build an UserMod out of connector object attributes and schema mapping.
+     *
+     * @param key user to be updated
+     * @param obj connector object
+     * @param original subject to get diff from
+     * @param syncTask synchronization task
+     * @param attrUtil AttributableUtil
+     * @param <T> user/role
+     * @return modifications for the user/role to be updated
+     * @throws NotFoundException if given id does not correspond to a T instance
+     * @throws UnauthorizedRoleException if there are no enough entitlements to access the T instance
+     */
+    @SuppressWarnings("unchecked")
+    @Transactional(readOnly = true)
+    public <T extends AbstractAttributableMod> T getAttributableMod(final Long key, final ConnectorObject obj,
+            final AbstractAttributableTO original, final SyncTask syncTask, final AttributableUtil attrUtil)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        final AbstractAttributableTO updated = getSubjectTOFromConnObject(obj, syncTask, attrUtil);
+        updated.setKey(key);
+
+        if (AttributableType.USER == attrUtil.getType()) {
+            // update password if and only if password is really changed
+            final User user = userDAO.authFecthUser(key);
+            if (StringUtils.isBlank(((UserTO) updated).getPassword())
+                    || encryptor.verify(((UserTO) updated).getPassword(),
+                            user.getCipherAlgorithm(), user.getPassword())) {
+
+                ((UserTO) updated).setPassword(null);
+            }
+
+            for (MembershipTO membTO : ((UserTO) updated).getMemberships()) {
+                Membership memb = user.getMembership(membTO.getRoleId());
+                if (memb != null) {
+                    membTO.setKey(memb.getKey());
+                }
+            }
+
+            return (T) AttributableOperations.diff(((UserTO) updated), ((UserTO) original), true);
+        }
+        if (AttributableType.ROLE == attrUtil.getType()) {
+            // reading from connector object cannot change entitlements
+            ((RoleTO) updated).getEntitlements().addAll(((RoleTO) original).getEntitlements());
+            return (T) AttributableOperations.diff(((RoleTO) updated), ((RoleTO) original), true);
+        }
+
+        return null;
+    }
+
+    private <T extends AbstractSubjectTO> T getSubjectTOFromConnObject(final ConnectorObject obj,
+            final SyncTask syncTask, final AttributableUtil attrUtil) {
+
+        final T subjectTO = attrUtil.newSubjectTO();
+
+        // 1. fill with data from connector object
+        for (MappingItem item : attrUtil.getUidToMappingItems(
+                syncTask.getResource(), MappingPurpose.SYNCHRONIZATION)) {
+
+            Attribute attribute = obj.getAttributeByName(item.getExtAttrName());
+
+            AttrTO attributeTO;
+            switch (item.getIntMappingType()) {
+                case UserId:
+                case RoleId:
+                    break;
+
+                case Password:
+                    if (subjectTO instanceof UserTO && attribute != null && attribute.getValue() != null
+                            && !attribute.getValue().isEmpty()) {
+
+                        ((UserTO) subjectTO).setPassword(getPassword(attribute.getValue().get(0)));
+                    }
+                    break;
+
+                case Username:
+                    if (subjectTO instanceof UserTO) {
+                        ((UserTO) subjectTO).setUsername(attribute == null || attribute.getValue().isEmpty()
+                                || attribute.getValue().get(0) == null
+                                        ? null
+                                        : attribute.getValue().get(0).toString());
+                    }
+                    break;
+
+                case RoleName:
+                    if (subjectTO instanceof RoleTO) {
+                        ((RoleTO) subjectTO).setName(attribute == null || attribute.getValue().isEmpty()
+                                || attribute.getValue().get(0) == null
+                                        ? null
+                                        : attribute.getValue().get(0).toString());
+                    }
+                    break;
+
+                case RoleOwnerSchema:
+                    if (subjectTO instanceof RoleTO && attribute != null) {
+                        // using a special attribute (with schema "", that will be ignored) for carrying the
+                        // RoleOwnerSchema value
+                        attributeTO = new AttrTO();
+                        attributeTO.setSchema(StringUtils.EMPTY);
+                        if (attribute.getValue().isEmpty() || attribute.getValue().get(0) == null) {
+                            attributeTO.getValues().add(StringUtils.EMPTY);
+                        } else {
+                            attributeTO.getValues().add(attribute.getValue().get(0).toString());
+                        }
+
+                        ((RoleTO) subjectTO).getPlainAttrs().add(attributeTO);
+                    }
+                    break;
+
+                case UserSchema:
+                case RoleSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+
+                    PlainSchema schema = plainSchemaDAO.find(item.getIntAttrName(), attrUtil.plainSchemaClass());
+
+                    for (Object value : attribute == null || attribute.getValue() == null
+                            ? Collections.emptyList()
+                            : attribute.getValue()) {
+
+                        AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType();
+                        if (value != null) {
+                            final PlainAttrValue attrValue = attrUtil.newPlainAttrValue();
+                            switch (schemaType) {
+                                case String:
+                                    attrValue.setStringValue(value.toString());
+                                    break;
+
+                                case Binary:
+                                    attrValue.setBinaryValue((byte[]) value);
+                                    break;
+
+                                default:
+                                    try {
+                                        attrValue.parseValue(schema, value.toString());
+                                    } catch (ParsingValidationException e) {
+                                        LOG.error("While parsing provided value {}", value, e);
+                                        attrValue.setStringValue(value.toString());
+                                        schemaType = AttrSchemaType.String;
+                                    }
+                            }
+                            attributeTO.getValues().add(attrValue.getValueAsString(schemaType));
+                        }
+                    }
+
+                    subjectTO.getPlainAttrs().add(attributeTO);
+                    break;
+
+                case UserDerivedSchema:
+                case RoleDerivedSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+                    subjectTO.getDerAttrs().add(attributeTO);
+                    break;
+
+                case UserVirtualSchema:
+                case RoleVirtualSchema:
+                    attributeTO = new AttrTO();
+                    attributeTO.setSchema(item.getIntAttrName());
+
+                    for (Object value : attribute == null || attribute.getValue() == null
+                            ? Collections.emptyList()
+                            : attribute.getValue()) {
+
+                        if (value != null) {
+                            attributeTO.getValues().add(value.toString());
+                        }
+                    }
+
+                    subjectTO.getVirAttrs().add(attributeTO);
+                    break;
+
+                default:
+            }
+        }
+
+        // 2. add data from defined template (if any)
+        AbstractSubjectTO template = AttributableType.USER == attrUtil.getType()
+                ? syncTask.getUserTemplate() : syncTask.getRoleTemplate();
+
+        if (template != null) {
+            if (template instanceof UserTO) {
+                if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
+                    String evaluated = JexlUtil.evaluate(((UserTO) template).getUsername(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((UserTO) subjectTO).setUsername(evaluated);
+                    }
+                }
+
+                if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
+                    String evaluated = JexlUtil.evaluate(((UserTO) template).getPassword(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((UserTO) subjectTO).setPassword(evaluated);
+                    }
+                }
+
+                Map<Long, MembershipTO> currentMembs = ((UserTO) subjectTO).getMembershipMap();
+                for (MembershipTO membTO : ((UserTO) template).getMemberships()) {
+                    MembershipTO membTBU;
+                    if (currentMembs.containsKey(membTO.getRoleId())) {
+                        membTBU = currentMembs.get(membTO.getRoleId());
+                    } else {
+                        membTBU = new MembershipTO();
+                        membTBU.setRoleId(membTO.getRoleId());
+                        ((UserTO) subjectTO).getMemberships().add(membTBU);
+                    }
+                    fillFromTemplate(membTBU, membTO);
+                }
+            }
+            if (template instanceof RoleTO) {
+                if (StringUtils.isNotBlank(((RoleTO) template).getName())) {
+                    String evaluated = JexlUtil.evaluate(((RoleTO) template).getName(), subjectTO);
+                    if (StringUtils.isNotBlank(evaluated)) {
+                        ((RoleTO) subjectTO).setName(evaluated);
+                    }
+                }
+
+                if (((RoleTO) template).getParent() != 0) {
+                    final Role parentRole = roleDAO.find(((RoleTO) template).getParent());
+                    if (parentRole != null) {
+                        ((RoleTO) subjectTO).setParent(parentRole.getKey());
+                    }
+                }
+
+                if (((RoleTO) template).getUserOwner() != null) {
+                    final User userOwner = userDAO.find(((RoleTO) template).getUserOwner());
+                    if (userOwner != null) {
+                        ((RoleTO) subjectTO).setUserOwner(userOwner.getKey());
+                    }
+                }
+                if (((RoleTO) template).getRoleOwner() != null) {
+                    final Role roleOwner = roleDAO.find(((RoleTO) template).getRoleOwner());
+                    if (roleOwner != null) {
+                        ((RoleTO) subjectTO).setRoleOwner(roleOwner.getKey());
+                    }
+                }
+
+                ((RoleTO) subjectTO).getEntitlements().addAll(((RoleTO) template).getEntitlements());
+
+                ((RoleTO) subjectTO).getRAttrTemplates().addAll(((RoleTO) template).getRAttrTemplates());
+                ((RoleTO) subjectTO).getRDerAttrTemplates().addAll(((RoleTO) template).getRDerAttrTemplates());
+                ((RoleTO) subjectTO).getRVirAttrTemplates().addAll(((RoleTO) template).getRVirAttrTemplates());
+                ((RoleTO) subjectTO).getMAttrTemplates().addAll(((RoleTO) template).getMAttrTemplates());
+                ((RoleTO) subjectTO).getMDerAttrTemplates().addAll(((RoleTO) template).getMDerAttrTemplates());
+                ((RoleTO) subjectTO).getMVirAttrTemplates().addAll(((RoleTO) template).getMVirAttrTemplates());
+
+                ((RoleTO) subjectTO).setAccountPolicy(((RoleTO) template).getAccountPolicy());
+                ((RoleTO) subjectTO).setPasswordPolicy(((RoleTO) template).getPasswordPolicy());
+
+                ((RoleTO) subjectTO).setInheritOwner(((RoleTO) template).isInheritOwner());
+                ((RoleTO) subjectTO).setInheritTemplates(((RoleTO) template).isInheritTemplates());
+                ((RoleTO) subjectTO).setInheritAttrs(((RoleTO) template).isInheritAttrs());
+                ((RoleTO) subjectTO).setInheritDerAttrs(((RoleTO) template).isInheritDerAttrs());
+                ((RoleTO) subjectTO).setInheritVirAttrs(((RoleTO) template).isInheritVirAttrs());
+                ((RoleTO) subjectTO).setInheritPasswordPolicy(((RoleTO) template).isInheritPasswordPolicy());
+                ((RoleTO) subjectTO).setInheritAccountPolicy(((RoleTO) template).isInheritAccountPolicy());
+            }
+
+            fillFromTemplate(subjectTO, template);
+
+            for (String resource : template.getResources()) {
+                subjectTO.getResources().add(resource);
+            }
+        }
+
+        return subjectTO;
+    }
+
+    /**
+     * Extract password value from passed value (if instance of GuardedString or GuardedByteArray).
+     *
+     * @param pwd received from the underlying connector
+     * @return password value
+     */
+    public String getPassword(final Object pwd) {
+        final StringBuilder result = new StringBuilder();
+
+        if (pwd instanceof GuardedString) {
+            ((GuardedString) pwd).access(new GuardedString.Accessor() {
+
+                @Override
+                public void access(final char[] clearChars) {
+                    result.append(clearChars);
+                }
+            });
+        } else if (pwd instanceof GuardedByteArray) {
+            ((GuardedByteArray) pwd).access(new GuardedByteArray.Accessor() {
+
+                @Override
+                public void access(final byte[] clearBytes) {
+                    result.append(new String(clearBytes));
+                }
+            });
+        } else if (pwd instanceof String) {
+            result.append((String) pwd);
+        } else {
+            result.append(pwd.toString());
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Get connector object TO from a connector object.
+     *
+     * @param connObject connector object.
+     * @return connector object TO.
+     */
+    public ConnObjectTO getConnObjectTO(final ConnectorObject connObject) {
+        final ConnObjectTO connObjectTO = new ConnObjectTO();
+
+        for (Attribute attr : connObject.getAttributes()) {
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(attr.getName());
+
+            if (attr.getValue() != null) {
+                for (Object value : attr.getValue()) {
+                    if (value != null) {
+                        if (value instanceof GuardedString || value instanceof GuardedByteArray) {
+                            attrTO.getValues().add(getPassword(value));
+                        } else if (value instanceof byte[]) {
+                            attrTO.getValues().add(Base64.encode((byte[]) value));
+                        } else {
+                            attrTO.getValues().add(value.toString());
+                        }
+                    }
+                }
+            }
+
+            connObjectTO.getPlainAttrs().add(attrTO);
+        }
+
+        return connObjectTO;
+    }
+
+    /**
+     * Query connected external resources for values to populated virtual attributes associated with the given owner.
+     *
+     * @param owner user or role
+     * @param attrUtil attributable util
+     */
+    public void retrieveVirAttrValues(final Attributable<?, ?, ?> owner, final AttributableUtil attrUtil) {
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final ConnectorFactory connFactory = context.getBean(ConnectorFactory.class);
+
+        final IntMappingType type = attrUtil.getType() == AttributableType.USER
+                ? IntMappingType.UserVirtualSchema : attrUtil.getType() == AttributableType.ROLE
+                        ? IntMappingType.RoleVirtualSchema : IntMappingType.MembershipVirtualSchema;
+
+        final Map<String, ConnectorObject> externalResources = new HashMap<>();
+
+        // -----------------------
+        // Retrieve virtual attribute values if and only if they have not been retrieved yet
+        // -----------------------
+        for (VirAttr virAttr : owner.getVirAttrs()) {
+            // reset value set
+            if (virAttr.getValues().isEmpty()) {
+                retrieveVirAttrValue(owner, virAttr, attrUtil, type, externalResources, connFactory);
+            }
+        }
+        // -----------------------
+    }
+
+    private void retrieveVirAttrValue(
+            final Attributable<?, ?, ?> owner,
+            final VirAttr virAttr,
+            final AttributableUtil attrUtil,
+            final IntMappingType type,
+            final Map<String, ConnectorObject> externalResources,
+            final ConnectorFactory connFactory) {
+
+        final String schemaName = virAttr.getSchema().getKey();
+        final VirAttrCacheValue virAttrCacheValue = virAttrCache.get(attrUtil.getType(), owner.getKey(), schemaName);
+
+        LOG.debug("Retrieve values for virtual attribute {} ({})", schemaName, type);
+
+        if (virAttrCache.isValidEntry(virAttrCacheValue)) {
+            // cached ...
+            LOG.debug("Values found in cache {}", virAttrCacheValue);
+            virAttr.getValues().clear();
+            virAttr.getValues().addAll(new ArrayList<>(virAttrCacheValue.getValues()));
+        } else {
+            // not cached ...
+            LOG.debug("Need one or more remote connections");
+
+            final VirAttrCacheValue toBeCached = new VirAttrCacheValue();
+
+            // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because 
+            // membership mapping is contained in user mapping
+            final Subject<?, ?, ?> realOwner = owner instanceof Membership
+                    ? ((Membership) owner).getUser()
+                    : (Subject) owner;
+
+            final Set<ExternalResource> targetResources = owner instanceof Membership
+                    ? getTargetResource(virAttr, type, attrUtil, realOwner.getResources())
+                    : getTargetResource(virAttr, type, attrUtil);
+
+            for (ExternalResource resource : targetResources) {
+                LOG.debug("Search values into {}", resource.getKey());
+                try {
+                    final List<MappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH);
+
+                    final ConnectorObject connectorObject;
+
+                    if (externalResources.containsKey(resource.getKey())) {
+                        connectorObject = externalResources.get(resource.getKey());
+                    } else {
+                        LOG.debug("Perform connection to {}", resource.getKey());
+                        final String accountId = attrUtil.getAccountIdItem(resource) == null
+                                ? null
+                                : MappingUtil.getAccountIdValue(
+                                        realOwner, resource, attrUtil.getAccountIdItem(resource));
+
+                        if (StringUtils.isBlank(accountId)) {
+                            throw new IllegalArgumentException("No AccountId found for " + resource.getKey());
+                        }
+
+                        final Connector connector = connFactory.getConnector(resource);
+
+                        final OperationOptions oo =
+                                connector.getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type));
+
+                        connectorObject = connector.getObject(fromSubject(realOwner), new Uid(accountId), oo);
+                        externalResources.put(resource.getKey(), connectorObject);
+                    }
+
+                    if (connectorObject != null) {
+                        // ask for searched virtual attribute value
+                        final List<MappingItem> virAttrMappings =
+                                MappingUtil.getMatchingMappingItems(mappings, schemaName, type);
+
+                        // the same virtual attribute could be mapped with one or more external attribute 
+                        for (MappingItem mapping : virAttrMappings) {
+                            final Attribute attribute = connectorObject.getAttributeByName(mapping.getExtAttrName());
+
+                            if (attribute != null && attribute.getValue() != null) {
+                                for (Object obj : attribute.getValue()) {
+                                    if (obj != null) {
+                                        virAttr.getValues().add(obj.toString());
+                                    }
+                                }
+                            }
+                        }
+
+                        toBeCached.setResourceValues(resource.getKey(), new HashSet<String>(virAttr.getValues()));
+
+                        LOG.debug("Retrieved values {}", virAttr.getValues());
+                    }
+                } catch (Exception e) {
+                    LOG.error("Error reading connector object from {}", resource.getKey(), e);
+
+                    if (virAttrCacheValue != null) {
+                        toBeCached.forceExpiring();
+                        LOG.debug("Search for a cached value (even expired!) ...");
+                        final Set<String> cachedValues = virAttrCacheValue.getValues(resource.getKey());
+                        if (cachedValues != null) {
+                            LOG.debug("Use cached value {}", cachedValues);
+                            virAttr.getValues().addAll(cachedValues);
+                            toBeCached.setResourceValues(resource.getKey(), new HashSet<>(cachedValues));
+                        }
+                    }
+                }
+            }
+
+            virAttrCache.put(attrUtil.getType(), owner.getKey(), schemaName, toBeCached);
+        }
+    }
+
+    private Set<ExternalResource> getTargetResource(
+            final VirAttr attr, final IntMappingType type, final AttributableUtil attrUtil) {
+
+        final Set<ExternalResource> resources = new HashSet<>();
+
+        if (attr.getOwner() instanceof Subject) {
+            for (ExternalResource res : ((Subject<?, ?, ?>) attr.getOwner()).getResources()) {
+                if (!MappingUtil.getMatchingMappingItems(
+                        attrUtil.getMappingItems(res, MappingPurpose.BOTH),
+                        attr.getSchema().getKey(), type).isEmpty()) {
+
+                    resources.add(res);
+                }
+            }
+        }
+
+        return resources;
+    }
+
+    private Set<ExternalResource> getTargetResource(final VirAttr attr, final IntMappingType type,
+            final AttributableUtil attrUtil, final Set<? extends ExternalResource> ownerResources) {
+
+        final Set<ExternalResource> resources = new HashSet<>();
+
+        for (ExternalResource res : ownerResources) {
+            if (!MappingUtil.getMatchingMappingItems(
+                    attrUtil.getMappingItems(res, MappingPurpose.BOTH),
+                    attr.getSchema().getKey(), type).isEmpty()) {
+
+                resources.add(res);
+            }
+        }
+
+        return resources;
+    }
+
+    private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) {
+        Map<String, AttrTO> currentAttrMap = attributableTO.getAttrMap();
+        for (AttrTO templateAttr : template.getPlainAttrs()) {
+            if (templateAttr.getValues() != null && !templateAttr.getValues().isEmpty()
+                    && (!currentAttrMap.containsKey(templateAttr.getSchema())
+                    || currentAttrMap.get(templateAttr.getSchema()).getValues().isEmpty())) {
+
+                attributableTO.getPlainAttrs().add(evaluateAttrTemplate(attributableTO, templateAttr));
+            }
+        }
+
+        currentAttrMap = attributableTO.getDerAttrMap();
+        for (AttrTO templateDerAttr : template.getDerAttrs()) {
+            if (!currentAttrMap.containsKey(templateDerAttr.getSchema())) {
+                attributableTO.getDerAttrs().add(templateDerAttr);
+            }
+        }
+
+        currentAttrMap = attributableTO.getVirAttrMap();
+        for (AttrTO templateVirAttr : template.getVirAttrs()) {
+            if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty()
+                    && (!currentAttrMap.containsKey(templateVirAttr.getSchema())
+                    || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) {
+
+                attributableTO.getVirAttrs().add(evaluateAttrTemplate(attributableTO, templateVirAttr));
+            }
+        }
+    }
+
+    private AttrTO evaluateAttrTemplate(final AbstractAttributableTO attributableTO, final AttrTO template) {
+        AttrTO result = new AttrTO();
+        result.setSchema(template.getSchema());
+
+        if (template.getValues() != null && !template.getValues().isEmpty()) {
+            for (String value : template.getValues()) {
+                String evaluated = JexlUtil.evaluate(value, attributableTO);
+                if (StringUtils.isNotBlank(evaluated)) {
+                    result.getValues().add(evaluated);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Transform a
+     * <code>Collection</code> of {@link Attribute} instances into a {@link Map}. The key to each element in the map is
+     * the <i>name</i> of an
+     * <code>Attribute</code>. The value of each element in the map is the
+     * <code>Attribute</code> instance with that name. <br/> Different from the original because: <ul> <li>map keys are
+     * transformed toUpperCase()</li> <li>returned map is mutable</li> </ul>
+     *
+     * @param attributes set of attribute to transform to a map.
+     * @return a map of string and attribute.
+     *
+     * @see org.identityconnectors.framework.common.objects.AttributeUtil#toMap(java.util.Collection)
+     */
+    public Map<String, Attribute> toMap(final Collection<? extends Attribute> attributes) {
+        final Map<String, Attribute> map = new HashMap<>();
+        for (Attribute attr : attributes) {
+            map.put(attr.getName().toUpperCase(), attr);
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
new file mode 100644
index 0000000..5a39faf
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/ExceptionUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.utils;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+public final class ExceptionUtil {
+
+    /**
+     * Uses commons lang's ExceptionUtils to provide a representation of the full stack trace of the given throwable.
+     *
+     * @param t throwable to build stack trace from
+     * @return a string representation of full stack trace of the given throwable
+     */
+    public static String getFullStackTrace(final Throwable t) {
+        StringBuilder result = new StringBuilder();
+
+        for (Throwable throwable : ExceptionUtils.getThrowableList(t)) {
+            result.append(ExceptionUtils.getMessage(throwable)).append('\n').
+                    append(ExceptionUtils.getStackTrace(throwable)).append("\n\n");
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private ExceptionUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
new file mode 100644
index 0000000..8c97a1c
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/InvalidPasswordPolicySpecException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.server.utils;
+
+/**
+ * Raise when the merge of two or more PasswordPolicySpec leds to incompatible condition.
+ *
+ * @see org.apache.syncope.common.lib.types.PasswordPolicySpec
+ */
+public class InvalidPasswordPolicySpecException extends Exception {
+
+    private static final long serialVersionUID = 4810651743226663580L;
+
+    public InvalidPasswordPolicySpecException(final String msg) {
+        super(msg);
+    }
+
+    public InvalidPasswordPolicySpecException(final String msg, final Exception e) {
+        super(msg, e);
+    }
+}


[10/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PropagationActionClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PropagationActionClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PropagationActionClass.java
new file mode 100644
index 0000000..f8e54b6
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PropagationActionClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "propagationActionClass")
+@XmlType
+public class PropagationActionClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 2187654394121198308L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PushActionClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PushActionClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PushActionClass.java
new file mode 100644
index 0000000..e12fd8e
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/PushActionClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "pushActionClass")
+@XmlType
+public class PushActionClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 1669581609310071906L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ReportletConfClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ReportletConfClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ReportletConfClass.java
new file mode 100644
index 0000000..24ba188
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ReportletConfClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "reportletConfClass")
+@XmlType
+public class ReportletConfClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 1343357929074360450L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ResourceName.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ResourceName.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ResourceName.java
new file mode 100644
index 0000000..42b77ca
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/ResourceName.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "resourceName")
+@XmlType
+public class ResourceName extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = -175720097924079573L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SubjectId.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SubjectId.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SubjectId.java
new file mode 100644
index 0000000..d61f1d9
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SubjectId.java
@@ -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.
+ */
+package org.apache.syncope.common.lib.wrap;
+
+public class SubjectId extends AbstractWrappable<Long> {
+
+    private static final long serialVersionUID = -8664228651057889297L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SyncActionClass.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SyncActionClass.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SyncActionClass.java
new file mode 100644
index 0000000..d6c7a77
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/SyncActionClass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "syncActionClass")
+@XmlType
+public class SyncActionClass extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 1669581609310071905L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/Validator.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/Validator.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/Validator.java
new file mode 100644
index 0000000..71cad00
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/Validator.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "validator")
+@XmlType
+public class Validator extends AbstractWrappable<String> {
+
+    private static final long serialVersionUID = 7233619557177034453L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/package-info.java
----------------------------------------------------------------------
diff --git a/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/package-info.java b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/package-info.java
new file mode 100644
index 0000000..397cb66
--- /dev/null
+++ b/syncope620/common/lib/src/main/java/org/apache/syncope/common/lib/wrap/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NAMESPACE)
+package org.apache.syncope.common.lib.wrap;
+
+import javax.xml.bind.annotation.XmlSchema;
+import org.apache.syncope.common.lib.SyncopeConstants;

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/pom.xml b/syncope620/pom.xml
index 82e910d..ad69b63 100644
--- a/syncope620/pom.xml
+++ b/syncope620/pom.xml
@@ -320,6 +320,8 @@ under the License.
     <connid.ldap.version>1.4.0</connid.ldap.version>
     <connid.ad.version>1.2.1</connid.ad.version>
 
+    <cxf.version>3.0.3</cxf.version>
+
     <jackson.version>2.5.0</jackson.version>
 
     <spring.version>4.1.4.RELEASE</spring.version>
@@ -333,7 +335,9 @@ under the License.
 
     <quartz.version>2.2.1</quartz.version>
 
-    <slf4j.version>1.7.9</slf4j.version>
+    <cocoon.version>3.0.0-alpha-3</cocoon.version>
+
+    <slf4j.version>1.7.10</slf4j.version>
     <log4j.version>2.1</log4j.version>
 
     <commons-io.version>2.4</commons-io.version>
@@ -379,6 +383,49 @@ under the License.
         <version>3.0.0</version>
         <scope>provided</scope>
       </dependency>
+      <dependency>
+        <groupId>javax.ws.rs</groupId>
+        <artifactId>javax.ws.rs-api</artifactId>
+        <version>2.0.1</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.geronimo.javamail</groupId>
+        <artifactId>geronimo-javamail_1.4_mail</artifactId>
+        <version>1.8.4</version>
+      </dependency>
+
+      <!-- CXF -->
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-rs-extension-providers</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-rs-extension-search</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-frontend-jaxws</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-rs-service-description</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-rs-client</artifactId>
+        <version>${cxf.version}</version>
+      </dependency>      
+      <!-- /CXF -->
       
       <dependency>
         <groupId>org.apache.openjpa</groupId>
@@ -436,6 +483,11 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.springframework</groupId>
+        <artifactId>spring-context-support</artifactId>
+        <version>${spring.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework</groupId>
         <artifactId>spring-tx</artifactId>
         <version>${spring.version}</version>
       </dependency>
@@ -454,6 +506,12 @@ under the License.
         <artifactId>spring-security-core</artifactId>
         <version>${spring-security.version}</version>
       </dependency>
+
+     <dependency>
+        <groupId>org.aspectj</groupId>
+        <artifactId>aspectjweaver</artifactId>
+        <version>1.8.4</version>
+      </dependency>
       
       <dependency>
         <groupId>commons-io</groupId>
@@ -517,6 +575,87 @@ under the License.
       </dependency>
       
       <dependency>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity</artifactId>
+        <version>1.7</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity-tools</artifactId>
+        <version>2.0</version>
+        <exclusions>
+          <exclusion>
+            <artifactId>struts-core</artifactId>
+            <groupId>org.apache.struts</groupId>
+          </exclusion>
+          <exclusion>
+            <artifactId>struts-taglib</artifactId>
+            <groupId>org.apache.struts</groupId>
+          </exclusion>
+          <exclusion>
+            <artifactId>struts-tiles</artifactId>
+            <groupId>org.apache.struts</groupId>
+          </exclusion>
+          <exclusion>
+            <artifactId>sslext</artifactId>
+            <groupId>sslext</groupId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      
+      <dependency>
+        <groupId>org.apache.cocoon.sax</groupId>
+        <artifactId>cocoon-sax</artifactId>
+        <version>${cocoon.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cocoon.optional</groupId>
+        <artifactId>cocoon-optional</artifactId>
+        <version>${cocoon.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.xmlgraphics</groupId>
+        <artifactId>fop</artifactId>
+        <version>1.1</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.apache.avalon.framework</groupId>
+            <artifactId>avalon-framework-api</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.apache.avalon.framework</groupId>
+            <artifactId>avalon-framework-impl</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>xalan</groupId>
+            <artifactId>xalan</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.avalon.framework</groupId>
+        <artifactId>avalon-framework-api</artifactId>
+        <version>4.3.1</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.avalon.framework</groupId>
+        <artifactId>avalon-framework-impl</artifactId>
+        <version>4.3.1</version>
+      </dependency>
+
+      <dependency>
+        <groupId>xalan</groupId>
+        <artifactId>xalan</artifactId>
+        <version>2.7.1</version>
+      </dependency>
+      <dependency>
+        <groupId>xerces</groupId>
+        <artifactId>xercesImpl</artifactId>
+        <version>2.11.0</version>
+      </dependency>
+
+      <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>${slf4j.version}</version>
@@ -526,6 +665,11 @@ under the License.
         <artifactId>log4j-api</artifactId>
         <version>${log4j.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-core</artifactId>
+        <version>${log4j.version}</version>
+      </dependency>
       
       <dependency>
         <groupId>org.springframework</groupId>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/pom.xml b/syncope620/server/logic/pom.xml
new file mode 100644
index 0000000..fd8fc42
--- /dev/null
+++ b/syncope620/server/logic/pom.xml
@@ -0,0 +1,127 @@
+<?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-server</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Server Logic</name>
+  <description>Apache Syncope Server Logic</description>
+  <groupId>org.apache.syncope.server</groupId>
+  <artifactId>syncope-server-logic</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.geronimo.javamail</groupId>
+      <artifactId>geronimo-javamail_1.4_mail</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-dbcp2</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context-support</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-jdbc</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.aspectj</groupId>
+      <artifactId>aspectjweaver</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.velocity</groupId>
+      <artifactId>velocity</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.velocity</groupId>
+      <artifactId>velocity-tools</artifactId>
+    </dependency>
+      
+    <dependency>
+      <groupId>org.apache.cocoon.sax</groupId>
+      <artifactId>cocoon-sax</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cocoon.optional</groupId>
+      <artifactId>cocoon-optional</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.xmlgraphics</groupId>
+      <artifactId>fop</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.avalon.framework</groupId>
+      <artifactId>avalon-framework-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.avalon.framework</groupId>
+      <artifactId>avalon-framework-impl</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>xalan</groupId>
+      <artifactId>xalan</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>xerces</groupId>
+      <artifactId>xercesImpl</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+    </dependency>
+      
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-persistence-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-workflow-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.server</groupId>
+      <artifactId>syncope-server-utils</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractLogic.java
new file mode 100644
index 0000000..11686a2
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractLogic.java
@@ -0,0 +1,58 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Superclass for all controllers.
+ *
+ * @param <T> transfer object used for input / output
+ */
+abstract class AbstractLogic<T extends AbstractBaseBean> {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractLogic.class);
+
+    /**
+     * Resolves stored bean (if existing) referred by the given CUD method.
+     * <br />
+     * Read-only methods will be unresolved for performance reasons.
+     *
+     * @param method method.
+     * @param args method arguments.
+     * @return referred stored bean.
+     * @throws UnresolvedReferenceException in case of failures, read-only methods and unresolved bean.
+     */
+    public T resolveBeanReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+        final Transactional transactional = method.getAnnotation(Transactional.class);
+        if (transactional != null && transactional.readOnly()) {
+            throw new UnresolvedReferenceException();
+        }
+        return resolveReference(method, args);
+    }
+
+    protected abstract T resolveReference(Method method, Object... args) throws UnresolvedReferenceException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractResourceAssociator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractResourceAssociator.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractResourceAssociator.java
new file mode 100644
index 0000000..2a20d1f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractResourceAssociator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.server.logic;
+
+import java.util.Collection;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+
+public abstract class AbstractResourceAssociator<T extends AbstractAttributableTO> extends AbstractLogic<T> {
+
+    public abstract T unlink(Long id, Collection<String> resources);
+
+    public abstract T link(Long id, Collection<String> resources);
+
+    public abstract T unassign(Long id, Collection<String> resources);
+
+    public abstract T assign(Long id, Collection<String> resources, boolean changepwd, String password);
+
+    public abstract T deprovision(Long userId, Collection<String> resources);
+
+    public abstract T provision(Long userId, Collection<String> resources, boolean changepwd, String password);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractSubjectLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractSubjectLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractSubjectLogic.java
new file mode 100644
index 0000000..a9117b8
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractSubjectLogic.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.logic;
+
+import java.util.List;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+
+public abstract class AbstractSubjectLogic<T extends AbstractSubjectTO, V extends AbstractSubjectMod>
+        extends AbstractResourceAssociator<T> {
+
+    public abstract T read(Long key);
+
+    public abstract int count();
+
+    public abstract T update(V attributableMod);
+
+    public abstract T delete(Long key);
+
+    public abstract List<T> list(int page, int size, List<OrderByClause> orderBy);
+
+    public abstract List<T> search(SearchCond searchCondition, int page, int size, List<OrderByClause> orderBy);
+
+    public abstract int searchCount(SearchCond searchCondition);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractTransactionalLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractTransactionalLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractTransactionalLogic.java
new file mode 100644
index 0000000..00526b8
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/AbstractTransactionalLogic.java
@@ -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.
+ */
+package org.apache.syncope.server.logic;
+
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Adds Spring's transactional support to {@link AbstractLogic}.
+ *
+ * @param <T> transfer object used for input / output
+ */
+@Transactional(rollbackFor = { Throwable.class })
+abstract class AbstractTransactionalLogic<T extends AbstractBaseBean> extends AbstractLogic<T> {
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConfigurationLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConfigurationLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConfigurationLogic.java
new file mode 100644
index 0000000..581f640
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConfigurationLogic.java
@@ -0,0 +1,157 @@
+/*
+ * 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.server.logic;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConfTO;
+import org.apache.syncope.common.lib.wrap.Validator;
+import org.apache.syncope.persistence.api.content.ContentExporter;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.entity.conf.CPlainAttr;
+import org.apache.syncope.persistence.api.entity.conf.CPlainSchema;
+import org.apache.syncope.server.logic.data.ConfigurationDataBinder;
+import org.apache.syncope.server.logic.init.ImplementationClassNamesLoader;
+import org.apache.syncope.server.logic.init.WorkflowAdapterLoader;
+import org.apache.syncope.server.logic.notification.NotificationManager;
+import org.apache.syncope.server.spring.ResourceWithFallbackLoader;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ConfigurationLogic extends AbstractTransactionalLogic<ConfTO> {
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private ConfigurationDataBinder binder;
+
+    @Autowired
+    private ContentExporter exporter;
+
+    @Autowired
+    private ImplementationClassNamesLoader classNamesLoader;
+
+    @javax.annotation.Resource(name = "velocityResourceLoader")
+    private ResourceWithFallbackLoader resourceLoader;
+
+    @Autowired
+    private WorkflowAdapterLoader wfAdapterLoader;
+
+    @PreAuthorize("hasRole('CONFIGURATION_DELETE')")
+    public void delete(final String key) {
+        confDAO.delete(key);
+    }
+
+    @PreAuthorize("hasRole('CONFIGURATION_LIST')")
+    public ConfTO list() {
+        return binder.getConfTO(confDAO.get());
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public AttrTO read(final String key) {
+        AttrTO result;
+
+        CPlainAttr conf = confDAO.find(key);
+        if (conf == null) {
+            CPlainSchema schema = plainSchemaDAO.find(key, CPlainSchema.class);
+            if (schema == null) {
+                throw new NotFoundException("Configuration key " + key);
+            }
+
+            result = new AttrTO();
+            result.setSchema(key);
+        } else {
+            result = binder.getAttrTO(conf);
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('CONFIGURATION_SET')")
+    public void set(final AttrTO value) {
+        confDAO.save(binder.getAttribute(value));
+    }
+
+    @PreAuthorize("hasRole('CONFIGURATION_LIST')")
+    public Set<String> getValidators() {
+        return classNamesLoader.getClassNames(ImplementationClassNamesLoader.Type.VALIDATOR);
+    }
+
+    @PreAuthorize("hasRole('CONFIGURATION_LIST')")
+    public Set<String> getMailTemplates() {
+        Set<String> htmlTemplates = new HashSet<String>();
+        Set<String> textTemplates = new HashSet<String>();
+
+        try {
+            for (Resource resource : resourceLoader.getResources(NotificationManager.MAIL_TEMPLATES + "*.vm")) {
+                String template = resource.getURL().toExternalForm();
+                if (template.endsWith(NotificationManager.MAIL_TEMPLATE_HTML_SUFFIX)) {
+                    htmlTemplates.add(
+                            template.substring(template.indexOf(NotificationManager.MAIL_TEMPLATES) + 14,
+                                    template.indexOf(NotificationManager.MAIL_TEMPLATE_HTML_SUFFIX)));
+                } else if (template.endsWith(NotificationManager.MAIL_TEMPLATE_TEXT_SUFFIX)) {
+                    textTemplates.add(
+                            template.substring(template.indexOf(NotificationManager.MAIL_TEMPLATES) + 14,
+                                    template.indexOf(NotificationManager.MAIL_TEMPLATE_TEXT_SUFFIX)));
+                } else {
+                    LOG.warn("Unexpected template found: {}, ignoring...", template);
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("While searching for class implementing {}", Validator.class.getName(), e);
+        }
+
+        // Only templates available both as HTML and TEXT are considered
+        htmlTemplates.retainAll(textTemplates);
+
+        return htmlTemplates;
+    }
+
+    @PreAuthorize("hasRole('CONFIGURATION_EXPORT')")
+    @Transactional(readOnly = true)
+    public void export(final OutputStream os) {
+        try {
+            exporter.export(os, wfAdapterLoader.getTablePrefix());
+            LOG.debug("Database content successfully exported");
+        } catch (Exception e) {
+            LOG.error("While exporting database content", e);
+        }
+    }
+
+    @Override
+    protected ConfTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConnectorLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConnectorLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConnectorLogic.java
new file mode 100644
index 0000000..ee550cd
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/ConnectorLogic.java
@@ -0,0 +1,341 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ConnBundleTO;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.persistence.api.dao.ConnInstanceDAO;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.provisioning.api.ConnIdBundleManager;
+import org.apache.syncope.provisioning.api.Connector;
+import org.apache.syncope.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.logic.data.ConnInstanceDataBinder;
+import org.identityconnectors.common.l10n.CurrentLocale;
+import org.identityconnectors.framework.api.ConfigurationProperties;
+import org.identityconnectors.framework.api.ConnectorInfo;
+import org.identityconnectors.framework.api.ConnectorKey;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class ConnectorLogic extends AbstractTransactionalLogic<ConnInstanceTO> {
+
+    @Autowired
+    private ConnIdBundleManager connIdBundleManager;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private ConnInstanceDAO connInstanceDAO;
+
+    @Autowired
+    private ConnInstanceDataBinder binder;
+
+    @Autowired
+    private ConnectorFactory connFactory;
+
+    @PreAuthorize("hasRole('CONNECTOR_CREATE')")
+    public ConnInstanceTO create(final ConnInstanceTO connInstanceTO) {
+        ConnInstance connInstance = binder.getConnInstance(connInstanceTO);
+        try {
+            connInstance = connInstanceDAO.save(connInstance);
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidConnInstance);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getConnInstanceTO(connInstance);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_UPDATE')")
+    public ConnInstanceTO update(final ConnInstanceTO connInstanceTO) {
+        ConnInstance connInstance = binder.updateConnInstance(connInstanceTO.getKey(), connInstanceTO);
+        try {
+            connInstance = connInstanceDAO.save(connInstance);
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidConnInstance);
+            ex.getElements().add(e.getMessage());
+            throw ex;
+        }
+
+        return binder.getConnInstanceTO(connInstance);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_DELETE')")
+    public ConnInstanceTO delete(final Long connInstanceId) {
+        ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + connInstanceId + "'");
+        }
+
+        if (!connInstance.getResources().isEmpty()) {
+            SyncopeClientException associatedResources = SyncopeClientException.build(
+                    ClientExceptionType.AssociatedResources);
+            for (ExternalResource resource : connInstance.getResources()) {
+                associatedResources.getElements().add(resource.getKey());
+            }
+            throw associatedResources;
+        }
+
+        ConnInstanceTO connToDelete = binder.getConnInstanceTO(connInstance);
+
+        connInstanceDAO.delete(connInstanceId);
+
+        return connToDelete;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_LIST')")
+    @Transactional(readOnly = true)
+    public List<ConnInstanceTO> list(final String lang) {
+        if (StringUtils.isBlank(lang)) {
+            CurrentLocale.set(Locale.ENGLISH);
+        } else {
+            CurrentLocale.set(new Locale(lang));
+        }
+
+        List<ConnInstance> connInstances = connInstanceDAO.findAll();
+
+        final List<ConnInstanceTO> connInstanceTOs = new ArrayList<>();
+
+        for (ConnInstance connector : connInstances) {
+            try {
+                connInstanceTOs.add(binder.getConnInstanceTO(connector));
+            } catch (NotFoundException e) {
+                LOG.error("Connector '{}#{}' not found", connector.getBundleName(), connector.getVersion());
+            }
+        }
+
+        return connInstanceTOs;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public ConnInstanceTO read(final Long connInstanceId) {
+        ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + connInstanceId + "'");
+        }
+
+        return binder.getConnInstanceTO(connInstance);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public List<ConnBundleTO> getBundles(final String lang) {
+        if (StringUtils.isBlank(lang)) {
+            CurrentLocale.set(Locale.ENGLISH);
+        } else {
+            CurrentLocale.set(new Locale(lang));
+        }
+
+        List<ConnBundleTO> connectorBundleTOs = new ArrayList<>();
+        for (Map.Entry<String, List<ConnectorInfo>> entry : connIdBundleManager.getConnectorInfos().entrySet()) {
+            for (ConnectorInfo bundle : entry.getValue()) {
+                ConnBundleTO connBundleTO = new ConnBundleTO();
+                connBundleTO.setDisplayName(bundle.getConnectorDisplayName());
+
+                connBundleTO.setLocation(entry.getKey());
+
+                ConnectorKey key = bundle.getConnectorKey();
+                connBundleTO.setBundleName(key.getBundleName());
+                connBundleTO.setConnectorName(key.getConnectorName());
+                connBundleTO.setVersion(key.getBundleVersion());
+
+                ConfigurationProperties properties = connIdBundleManager.getConfigurationProperties(bundle);
+
+                for (String propName : properties.getPropertyNames()) {
+                    connBundleTO.getProperties().add(binder.buildConnConfPropSchema(properties.getProperty(propName)));
+                }
+
+                LOG.debug("Connector bundle: {}", connBundleTO);
+
+                connectorBundleTOs.add(connBundleTO);
+            }
+        }
+
+        return connectorBundleTOs;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public List<String> getSchemaNames(final ConnInstanceTO connInstanceTO, final boolean includeSpecial) {
+        final ConnInstance connInstance = connInstanceDAO.find(connInstanceTO.getKey());
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + connInstanceTO.getKey() + "'");
+        }
+
+        // consider the possibility to receive overridden properties only
+        final Set<ConnConfProperty> conf = binder.mergeConnConfProperties(connInstanceTO.getConfiguration(),
+                connInstance.getConfiguration());
+
+        // We cannot use Spring bean because this method could be used during resource definition or modification:
+        // bean couldn't exist or couldn't be updated.
+        // This is the reason why we should take a "not mature" connector facade proxy to ask for schema names.
+        final List<String> result = new ArrayList<>(connFactory.createConnector(connInstance, conf).
+                getSchemaNames(includeSpecial));
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public List<String> getSupportedObjectClasses(final ConnInstanceTO connInstanceTO) {
+        final ConnInstance connInstance = connInstanceDAO.find(connInstanceTO.getKey());
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + connInstanceTO.getKey() + "'");
+        }
+
+        // consider the possibility to receive overridden properties only
+        final Set<ConnConfProperty> conf = binder.mergeConnConfProperties(connInstanceTO.getConfiguration(),
+                connInstance.getConfiguration());
+
+        // We cannot use Spring bean because this method could be used during resource definition or modification:
+        // bean couldn't exist or couldn't be updated.
+        // This is the reason why we should take a "not mature" connector facade proxy to ask for object classes.
+        Set<ObjectClass> objectClasses = connFactory.createConnector(connInstance, conf).getSupportedObjectClasses();
+
+        List<String> result = new ArrayList<>(objectClasses.size());
+        for (ObjectClass objectClass : objectClasses) {
+            result.add(objectClass.getObjectClassValue());
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public List<ConnConfProperty> getConfigurationProperties(final Long connInstanceId) {
+
+        final ConnInstance connInstance = connInstanceDAO.find(connInstanceId);
+        if (connInstance == null) {
+            throw new NotFoundException("Connector '" + connInstanceId + "'");
+        }
+
+        return new ArrayList<ConnConfProperty>(connInstance.getConfiguration());
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public boolean check(final ConnInstanceTO connInstanceTO) {
+        final Connector connector = connFactory.createConnector(
+                binder.getConnInstance(connInstanceTO), connInstanceTO.getConfiguration());
+
+        boolean result;
+        try {
+            connector.test();
+            result = true;
+        } catch (Exception ex) {
+            LOG.error("Test connection failure {}", ex);
+            result = false;
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_READ')")
+    @Transactional(readOnly = true)
+    public ConnInstanceTO readByResource(final String resourceName) {
+        ExternalResource resource = resourceDAO.find(resourceName);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceName + "'");
+        }
+        return binder.getConnInstanceTO(connFactory.getConnector(resource).getActiveConnInstance());
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_RELOAD')")
+    @Transactional(readOnly = true)
+    public void reload() {
+        connFactory.unload();
+        connFactory.load();
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        if (bulkAction.getOperation() == BulkAction.Type.DELETE) {
+            for (String id : bulkAction.getTargets()) {
+                try {
+                    res.add(delete(Long.valueOf(id)).getKey(), BulkActionResult.Status.SUCCESS);
+                } catch (Exception e) {
+                    LOG.error("Error performing delete for connector {}", id, e);
+                    res.add(id, BulkActionResult.Status.FAILURE);
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected ConnInstanceTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof ConnInstanceTO) {
+                    id = ((ConnInstanceTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getConnInstanceTO(connInstanceDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/EntitlementLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/EntitlementLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/EntitlementLogic.java
new file mode 100644
index 0000000..e71fcb9
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/EntitlementLogic.java
@@ -0,0 +1,58 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.wrap.EntitlementTO;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.entity.Entitlement;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EntitlementLogic extends AbstractTransactionalLogic<EntitlementTO> {
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    public List<String> getAll() {
+        List<Entitlement> entitlements = entitlementDAO.findAll();
+        List<String> result = new ArrayList<String>(entitlements.size());
+        for (Entitlement entitlement : entitlements) {
+            result.add(entitlement.getKey());
+        }
+
+        return result;
+    }
+
+    public Set<String> getOwn() {
+        return AuthContextUtil.getOwnedEntitlementNames();
+    }
+
+    @Override
+    protected EntitlementTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LoggerLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LoggerLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LoggerLogic.java
new file mode 100644
index 0000000..5356b86
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LoggerLogic.java
@@ -0,0 +1,307 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.EventCategoryTO;
+import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.common.lib.types.LoggerType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.LoggerDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.Logger;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.SystemPropertyUtils;
+
+@Component
+public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
+
+    @Autowired
+    private LoggerDAO loggerDAO;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    private List<LoggerTO> list(final LoggerType type) {
+        List<LoggerTO> result = new ArrayList<>();
+        for (Logger syncopeLogger : loggerDAO.findAll(type)) {
+            LoggerTO loggerTO = new LoggerTO();
+            BeanUtils.copyProperties(syncopeLogger, loggerTO);
+            result.add(loggerTO);
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('LOG_LIST')")
+    @Transactional(readOnly = true)
+    public List<LoggerTO> listLogs() {
+        return list(LoggerType.LOG);
+    }
+
+    @PreAuthorize("hasRole('AUDIT_LIST')")
+    @Transactional(readOnly = true)
+    public List<AuditLoggerName> listAudits() {
+        List<AuditLoggerName> result = new ArrayList<>();
+
+        for (LoggerTO logger : list(LoggerType.AUDIT)) {
+            try {
+                result.add(AuditLoggerName.fromLoggerName(logger.getName()));
+            } catch (Exception e) {
+                LOG.warn("Unexpected audit logger name: {}", logger.getName(), e);
+            }
+        }
+
+        return result;
+    }
+
+    private void throwInvalidLogger(final LoggerType type) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
+        sce.getElements().add("Expected " + type.name());
+
+        throw sce;
+    }
+
+    private LoggerTO setLevel(final String name, final Level level, final LoggerType expectedType) {
+        Logger syncopeLogger = loggerDAO.find(name);
+        if (syncopeLogger == null) {
+            LOG.debug("Logger {} not found: creating new...", name);
+
+            syncopeLogger = entityFactory.newEntity(Logger.class);
+            syncopeLogger.setKey(name);
+            syncopeLogger.setType(name.startsWith(LoggerType.AUDIT.getPrefix())
+                    ? LoggerType.AUDIT
+                    : LoggerType.LOG);
+        }
+
+        if (expectedType != syncopeLogger.getType()) {
+            throwInvalidLogger(expectedType);
+        }
+
+        syncopeLogger.setLevel(LoggerLevel.fromLevel(level));
+        syncopeLogger = loggerDAO.save(syncopeLogger);
+
+        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name)
+                ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
+                : ctx.getConfiguration().getLoggerConfig(name);
+        logConf.setLevel(level);
+        ctx.updateLoggers();
+
+        LoggerTO result = new LoggerTO();
+        BeanUtils.copyProperties(syncopeLogger, result);
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('LOG_SET_LEVEL')")
+    public LoggerTO setLogLevel(final String name, final Level level) {
+        return setLevel(name, level, LoggerType.LOG);
+    }
+
+    @PreAuthorize("hasRole('AUDIT_ENABLE')")
+    public void enableAudit(final AuditLoggerName auditLoggerName) {
+        try {
+            setLevel(auditLoggerName.toLoggerName(), Level.DEBUG, LoggerType.AUDIT);
+        } catch (IllegalArgumentException e) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+    }
+
+    private LoggerTO delete(final String name, final LoggerType expectedType) throws NotFoundException {
+        Logger syncopeLogger = loggerDAO.find(name);
+        if (syncopeLogger == null) {
+            throw new NotFoundException("Logger " + name);
+        } else if (expectedType != syncopeLogger.getType()) {
+            throwInvalidLogger(expectedType);
+        }
+
+        LoggerTO loggerToDelete = new LoggerTO();
+        BeanUtils.copyProperties(syncopeLogger, loggerToDelete);
+
+        // remove SyncopeLogger from local storage, so that LoggerLoader won't load this next time
+        loggerDAO.delete(syncopeLogger);
+
+        // set log level to OFF in order to disable configured logger until next reboot
+        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        org.apache.logging.log4j.core.Logger logger = SyncopeConstants.ROOT_LOGGER.equals(name)
+                ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME) : ctx.getLogger(name);
+        logger.setLevel(Level.OFF);
+        ctx.updateLoggers();
+
+        return loggerToDelete;
+    }
+
+    @PreAuthorize("hasRole('LOG_DELETE')")
+    public LoggerTO deleteLog(final String name) throws NotFoundException {
+        return delete(name, LoggerType.LOG);
+    }
+
+    @PreAuthorize("hasRole('AUDIT_DISABLE')")
+    public void disableAudit(final AuditLoggerName auditLoggerName) {
+        try {
+            delete(auditLoggerName.toLoggerName(), LoggerType.AUDIT);
+        } catch (NotFoundException e) {
+            LOG.debug("Ignoring disable of non existing logger {}", auditLoggerName.toLoggerName());
+        } catch (IllegalArgumentException e) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+    }
+
+    @PreAuthorize("hasRole('AUDIT_LIST') or hasRole('NOTIFICATION_LIST')")
+    public List<EventCategoryTO> listAuditEvents() {
+        // use set to avoi duplications or null elements
+        final Set<EventCategoryTO> events = new HashSet<EventCategoryTO>();
+
+        try {
+            final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+            final MetadataReaderFactory metadataReaderFactory =
+                    new CachingMetadataReaderFactory(resourcePatternResolver);
+
+            final String packageSearchPath =
+                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+                    + ClassUtils.convertClassNameToResourcePath(
+                            SystemPropertyUtils.resolvePlaceholders(this.getClass().getPackage().getName()))
+                    + "/" + "**/*.class";
+
+            final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
+            for (Resource resource : resources) {
+                if (resource.isReadable()) {
+                    final MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
+                    final Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
+
+                    if (clazz.isAnnotationPresent(Component.class)
+                            && AbstractLogic.class.isAssignableFrom(clazz)) {
+                        final EventCategoryTO eventCategoryTO = new EventCategoryTO();
+                        eventCategoryTO.setCategory(clazz.getSimpleName());
+                        for (Method method : clazz.getDeclaredMethods()) {
+                            if (Modifier.isPublic(method.getModifiers())) {
+                                eventCategoryTO.getEvents().add(method.getName());
+                            }
+                        }
+                        events.add(eventCategoryTO);
+                    }
+                }
+            }
+
+            //SYNCOPE-608
+            final EventCategoryTO authenticationControllerEvents = new EventCategoryTO();
+            authenticationControllerEvents.setCategory("AuthenticationController");
+            authenticationControllerEvents.getEvents().add("login");
+            events.add(authenticationControllerEvents);
+
+            events.add(new EventCategoryTO(EventCategoryType.PROPAGATION));
+            events.add(new EventCategoryTO(EventCategoryType.SYNCHRONIZATION));
+            events.add(new EventCategoryTO(EventCategoryType.PUSH));
+
+            for (AttributableType attributableType : AttributableType.values()) {
+                for (ExternalResource resource : resourceDAO.findAll()) {
+                    final EventCategoryTO propEventCategoryTO = new EventCategoryTO(EventCategoryType.PROPAGATION);
+                    final EventCategoryTO syncEventCategoryTO = new EventCategoryTO(EventCategoryType.SYNCHRONIZATION);
+                    final EventCategoryTO pushEventCategoryTO = new EventCategoryTO(EventCategoryType.PUSH);
+
+                    propEventCategoryTO.setCategory(attributableType.name().toLowerCase());
+                    propEventCategoryTO.setSubcategory(resource.getKey());
+
+                    syncEventCategoryTO.setCategory(attributableType.name().toLowerCase());
+                    pushEventCategoryTO.setCategory(attributableType.name().toLowerCase());
+                    syncEventCategoryTO.setSubcategory(resource.getKey());
+                    pushEventCategoryTO.setSubcategory(resource.getKey());
+
+                    for (ResourceOperation resourceOperation : ResourceOperation.values()) {
+                        propEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
+                        syncEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
+                        pushEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
+                    }
+
+                    events.add(propEventCategoryTO);
+                    events.add(syncEventCategoryTO);
+                    events.add(pushEventCategoryTO);
+                }
+            }
+
+            for (SchedTask task : taskDAO.<SchedTask>findAll(TaskType.SCHEDULED)) {
+                final EventCategoryTO eventCategoryTO = new EventCategoryTO(EventCategoryType.TASK);
+                eventCategoryTO.setCategory(Class.forName(task.getJobClassName()).getSimpleName());
+                events.add(eventCategoryTO);
+            }
+
+            for (SyncTask task : taskDAO.<SyncTask>findAll(TaskType.SYNCHRONIZATION)) {
+                final EventCategoryTO eventCategoryTO = new EventCategoryTO(EventCategoryType.TASK);
+                eventCategoryTO.setCategory(Class.forName(task.getJobClassName()).getSimpleName());
+                events.add(eventCategoryTO);
+            }
+        } catch (Exception e) {
+            LOG.error("Failure retrieving audit/notification events", e);
+        }
+
+        return new ArrayList<EventCategoryTO>(events);
+    }
+
+    @Override
+    protected LoggerTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LogicInvocationHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LogicInvocationHandler.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LogicInvocationHandler.java
new file mode 100644
index 0000000..81ca56f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/LogicInvocationHandler.java
@@ -0,0 +1,108 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.server.logic.audit.AuditManager;
+import org.apache.syncope.server.logic.notification.NotificationManager;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Aspect
+public class LogicInvocationHandler {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(LogicInvocationHandler.class);
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Around("execution(* org.apache.syncope.server.logic.AbstractLogic+.*(..))")
+    public Object around(final ProceedingJoinPoint joinPoint) throws Throwable {
+        final Class<?> clazz = joinPoint.getTarget().getClass();
+
+        final Object[] input = joinPoint.getArgs();
+
+        final String category = clazz.getSimpleName();
+
+        final MethodSignature ms = (MethodSignature) joinPoint.getSignature();
+        Method method = ms.getMethod();
+
+        final String event = joinPoint.getSignature().getName();
+
+        AuditElements.Result result = null;
+        Object output = null;
+        Object before = null;
+
+        try {
+            LOG.debug("Before {}.{}({})", clazz.getSimpleName(), event,
+                    input == null || input.length == 0 ? "" : Arrays.asList(input));
+
+            try {
+                before = ((AbstractLogic) joinPoint.getTarget()).resolveBeanReference(method, input);
+            } catch (UnresolvedReferenceException ignore) {
+                LOG.debug("Unresolved bean reference ...");
+            }
+
+            output = joinPoint.proceed();
+            result = AuditElements.Result.SUCCESS;
+
+            LOG.debug("After returning {}.{}: {}", clazz.getSimpleName(), event, output);
+            return output;
+        } catch (Throwable t) {
+            output = t;
+            result = AuditElements.Result.FAILURE;
+
+            LOG.debug("After throwing {}.{}", clazz.getSimpleName(), event);
+            throw t;
+        } finally {
+            notificationManager.createTasks(
+                    AuditElements.EventCategoryType.REST,
+                    category,
+                    null,
+                    event,
+                    result,
+                    before,
+                    output,
+                    input);
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.REST,
+                    category,
+                    null,
+                    event,
+                    result,
+                    before,
+                    output,
+                    input);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/NotificationController.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/NotificationController.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/NotificationController.java
new file mode 100644
index 0000000..053cf3f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/NotificationController.java
@@ -0,0 +1,127 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.persistence.api.entity.Notification;
+import org.apache.syncope.server.logic.data.NotificationDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NotificationController extends AbstractTransactionalLogic<NotificationTO> {
+
+    @Autowired
+    private NotificationDAO notificationDAO;
+
+    @Autowired
+    private NotificationDataBinder binder;
+
+    @PreAuthorize("hasRole('NOTIFICATION_READ')")
+    public NotificationTO read(final Long notificationId) {
+        Notification notification = notificationDAO.find(notificationId);
+        if (notification == null) {
+            LOG.error("Could not find notification '" + notificationId + "'");
+
+            throw new NotFoundException(String.valueOf(notificationId));
+        }
+
+        return binder.getNotificationTO(notification);
+    }
+
+    @PreAuthorize("hasRole('NOTIFICATION_LIST')")
+    public List<NotificationTO> list() {
+        List<Notification> notifications = notificationDAO.findAll();
+
+        List<NotificationTO> notificationTOs = new ArrayList<NotificationTO>();
+        for (Notification notification : notifications) {
+            notificationTOs.add(binder.getNotificationTO(notification));
+        }
+
+        return notificationTOs;
+    }
+
+    @PreAuthorize("hasRole('NOTIFICATION_CREATE')")
+    public NotificationTO create(final NotificationTO notificationTO) {
+        return binder.getNotificationTO(notificationDAO.save(binder.create(notificationTO)));
+    }
+
+    @PreAuthorize("hasRole('NOTIFICATION_UPDATE')")
+    public NotificationTO update(final NotificationTO notificationTO) {
+        Notification notification = notificationDAO.find(notificationTO.getKey());
+        if (notification == null) {
+            LOG.error("Could not find notification '" + notificationTO.getKey() + "'");
+            throw new NotFoundException(String.valueOf(notificationTO.getKey()));
+        }
+
+        binder.update(notification, notificationTO);
+        notification = notificationDAO.save(notification);
+
+        return binder.getNotificationTO(notification);
+    }
+
+    @PreAuthorize("hasRole('CONNECTOR_DELETE')")
+    public NotificationTO delete(final Long notificationId) {
+        Notification notification = notificationDAO.find(notificationId);
+        if (notification == null) {
+            LOG.error("Could not find notification '" + notificationId + "'");
+
+            throw new NotFoundException(String.valueOf(notificationId));
+        }
+
+        NotificationTO deleted = binder.getNotificationTO(notification);
+        notificationDAO.delete(notificationId);
+        return deleted;
+    }
+
+    @Override
+    protected NotificationTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        Long id = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof NotificationTO) {
+                    id = ((NotificationTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return binder.getNotificationTO(notificationDAO.find(id));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}


[03/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/resources/content.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/resources/content.xml b/syncope620/server/persistence-jpa/src/test/resources/content.xml
index 4f024e0..04ee462 100644
--- a/syncope620/server/persistence-jpa/src/test/resources/content.xml
+++ b/syncope620/server/persistence-jpa/src/test/resources/content.xml
@@ -143,7 +143,7 @@ under the License.
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
   <SyncopeRole id="6"
                name="director" parent_id="4" userOwner_id="5"
-               inheritAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
+               inheritPlainAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
                inheritPasswordPolicy="0" inheritAccountPolicy="0"
                passwordPolicy_id="4"
                accountPolicy_id="6"
@@ -151,7 +151,7 @@ under the License.
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
   <SyncopeRole id="7"
                name="managingDirector" parent_id="6"
-               inheritAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
+               inheritPlainAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
                inheritPasswordPolicy="1" inheritAccountPolicy="1" inheritOwner="1"
                creator="admin" lastModifier="admin" 
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
@@ -166,7 +166,7 @@ under the License.
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
   <SyncopeRole id="10"
                name="managingConsultant" parent_id="6"
-               inheritAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
+               inheritPlainAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
                inheritPasswordPolicy="1" inheritAccountPolicy="1" inheritOwner="0"
                creator="admin" lastModifier="admin" 
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
@@ -181,7 +181,7 @@ under the License.
                creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
   <SyncopeRole id="14"
                name="artDirector" parent_id="4"
-               inheritAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
+               inheritPlainAttrs="1" inheritDerAttrs="1" inheritVirAttrs="1"
                inheritPasswordPolicy="0" inheritAccountPolicy="0"
                passwordPolicy_id="8"
                accountPolicy_id="6"
@@ -904,8 +904,8 @@ under the License.
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="4" name="CSV (update matching; assign unmatching)" resource_name="resource-csv"
         performCreate="1" performUpdate="1" performDelete="1" syncStatus="1" fullReconciliation="0"
         jobClassName="org.apache.syncope.provisioning.api.job.SyncJob" unmatchingRule="ASSIGN" matchingRule="UPDATE"
-        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"type","readonly":false,"values":["email == &apos;test8@syncope.apache.org&apos;? &apos;TYPE_8&apos;: &apos;TYPE_OTHER&apos;"]}],"derivedAttributes":[{"schema":"cn","readonly":false,"values":[null]}],"virtualAttributes":[],"resources":["resource-testdb"],"propagationStatuses":[],"memberships":[{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"roleId":8,"roleName":null,"attributes":[{"schema":"subscriptionDate","readonly":false,"values":["&apos;2009-08-18T16:33:12.203+0200&apos;"]}],"derivedAttributes":[],"virtualAttributes":[]}]}'
-        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
+        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"type","readonly":false,"values":["email == &apos;test8@syncope.apache.org&apos;? &apos;TYPE_8&apos;: &apos;TYPE_OTHER&apos;"]}],"derivedAttributes":[{"schema":"cn","readonly":false,"values":[null]}],"virtualAttributes":[],"resources":["resource-testdb"],"propagationStatuses":[],"memberships":[{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"roleId":8,"roleName":null,"attributes":[{"schema":"subscriptionDate","readonly":false,"values":["&apos;2009-08-18T16:33:12.203+0200&apos;"]}],"derivedAttributes":[],"virtualAttributes":[]}]}'
+        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritPlainAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
   <Task DTYPE="SchedTask" type="SCHEDULED" id="5" name="SampleJob Task" jobClassName="org.apache.syncope.core.quartz.SampleJob" cronExpression="0 0 0 1 * ?"/>
   <Task DTYPE="PropagationTask" type="PROPAGATION" id="6" propagationMode="TWO_PHASES" propagationOperation="UPDATE"
         objectClassName="__ACCOUNT__" resource_name="ws-target-resource-nopropagation" subjectType="USER" subjectId="1"
@@ -914,8 +914,8 @@ under the License.
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="7" name="TestDB Task" resource_name="resource-testdb"
         performCreate="1" performUpdate="1" performDelete="0" syncStatus="1" fullReconciliation="1"
         jobClassName="org.apache.syncope.provisioning.api.job.SyncJob" unmatchingRule="PROVISION" matchingRule="UPDATE"
-        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"type","readonly":false,"values":["&apos;type a&apos;"]},{"schema":"userId","readonly":false,"values":["&apos;reconciled@syncope.apache.org&apos;"]},{"schema":"fullname","readonly":false,"values":["&apos;reconciled fullname&apos;"]},{"schema":"surname","readonly":false,"values":["&apos;surname&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"memberships":[]}'
-        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
+        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"type","readonly":false,"values":["&apos;type a&apos;"]},{"schema":"userId","readonly":false,"values":["&apos;reconciled@syncope.apache.org&apos;"]},{"schema":"fullname","readonly":false,"values":["&apos;reconciled fullname&apos;"]},{"schema":"surname","readonly":false,"values":["&apos;surname&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"memberships":[]}'
+        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritPlainAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
   <Task DTYPE="NotificationTask" type="NOTIFICATION" id="8" sender="admin@prova.org" subject="Notification for SYNCOPE-81" 
         textBody="NOTIFICATION-81" htmlBody="NOTIFICATION-81" traceLevel="ALL"/>
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="9" name="TestDB2 Task" resource_name="resource-testdb2"
@@ -927,8 +927,8 @@ under the License.
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="11" name="LDAP Sync Task" resource_name="resource-ldap"
         fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0"
         jobClassName="org.apache.syncope.provisioning.api.job.SyncJob" unmatchingRule="PROVISION" matchingRule="UPDATE"
-        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[{"schema":"virtualReadOnly","readonly":false,"values":[""]}],"resources":["resource-ldap"],"propagationStatuses":[],"memberships":[]}'
-        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"name":null,"parent":8,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[{"schema":"show","readonly":false,"values":["&apos;true&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":["show"],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
+        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[{"schema":"virtualReadOnly","readonly":false,"values":[""]}],"resources":["resource-ldap"],"propagationStatuses":[],"memberships":[]}'
+        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"name":null,"parent":8,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritPlainAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[{"schema":"show","readonly":false,"values":["&apos;true&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":["show"],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
   <SyncTask_actionsClassNames SyncTask_id="11" actionClassName="org.apache.syncope.core.sync.impl.LDAPMembershipSyncActions"/>
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="12" name="VirAttrCache test" resource_name="resource-csv"
         performCreate="0" performUpdate="1" performDelete="0" syncStatus="0" fullReconciliation="1"
@@ -980,8 +980,8 @@ under the License.
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="24" name="CSV Task (update matching; provision unmatching)" resource_name="resource-csv"
         performCreate="1" performUpdate="1" performDelete="1" syncStatus="1" fullReconciliation="0"
         jobClassName="org.apache.syncope.provisioning.api.job.SyncJob" unmatchingRule="PROVISION" matchingRule="UPDATE"
-        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"firstname","readonly":false,"values":[""]},{"schema":"userId","readonly":false,"values":["&apos;test&apos;"]},{"schema":"fullname","readonly":false,"values":["&apos;test&apos;"]},{"schema":"surname","readonly":false,"values":["&apos;test&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":["resource-testdb"],"propagationStatuses":[],"memberships":[]}'
-        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"id":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
+        userTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"password":null,"status":null,"token":null,"tokenExpireTime":null,"username":null,"lastLoginDate":null,"changePwdDate":null,"failedLogins":null,"attributes":[{"schema":"firstname","readonly":false,"values":[""]},{"schema":"userId","readonly":false,"values":["&apos;test&apos;"]},{"schema":"fullname","readonly":false,"values":["&apos;test&apos;"]},{"schema":"surname","readonly":false,"values":["&apos;test&apos;"]}],"derivedAttributes":[],"virtualAttributes":[],"resources":["resource-testdb"],"propagationStatuses":[],"memberships":[]}'
+        roleTemplate='{"creator":null,"creationDate":null,"lastModifier":null,"lastChangeDate":null,"key":0,"name":null,"parent":0,"userOwner":null,"roleOwner":null,"inheritOwner":false,"inheritTemplates":false,"inheritPlainAttrs":false,"inheritDerAttrs":false,"inheritVirAttrs":false,"inheritPasswordPolicy":false,"inheritAccountPolicy":false,"passwordPolicy":null,"accountPolicy":null,"attributes":[],"derivedAttributes":[],"virtualAttributes":[],"resources":[],"propagationStatuses":[],"entitlements":[],"rAttrTemplates":[],"rDerAttrTemplates":[],"rVirAttrTemplates":[],"mAttrTemplates":[],"mDerAttrTemplates":[],"mVirAttrTemplates":[]}'/>
   <Task DTYPE="SyncTask" type="SYNCHRONIZATION" id="25" name="CSV (unlink matching; ignore unmatching)" resource_name="resource-csv"
         performCreate="1" performUpdate="1" performDelete="1" syncStatus="1" fullReconciliation="0"
         jobClassName="org.apache.syncope.provisioning.api.job.SyncJob" unmatchingRule="IGNORE" matchingRule="UNLINK"/>
@@ -1011,7 +1011,7 @@ under the License.
   
   <Report id="1" name="test"/>
   <ReportletConfInstance id="1" Report_id="1" 
-                         serializedInstance='{"@class":"org.apache.syncope.common.lib.report.UserReportletConf","name":"testUserReportlet","matchingCond":null,"attributes":["fullname","gender"],"derivedAttributes":["cn"],"virtualAttributes":["virtualdata"],"features":["id","username","workflowId","status","creationDate","lastLoginDate","changePwdDate","passwordHistorySize","failedLoginCount","memberships","resources"]}'/>
+                         serializedInstance='{"@class":"org.apache.syncope.common.lib.report.UserReportletConf","name":"testUserReportlet","matchingCond":null,"attributes":["fullname","gender"],"derivedAttributes":["cn"],"virtualAttributes":["virtualdata"],"features":["key","username","workflowId","status","creationDate","lastLoginDate","changePwdDate","passwordHistorySize","failedLoginCount","memberships","resources"]}'/>
   <ReportExec Report_id="1" id="1" status="SUCCESS" startDate="2012-02-26 15:40:04" endDate="2012-02-26 15:41:04"/>
   
   <SyncopeLogger logName="syncope.audit.[REST]:[EntitlementController]:[]:[getOwn]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/resources/persistenceTestEnv.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/resources/persistenceTestEnv.xml b/syncope620/server/persistence-jpa/src/test/resources/persistenceTestEnv.xml
index 53bc496..0170569 100644
--- a/syncope620/server/persistence-jpa/src/test/resources/persistenceTestEnv.xml
+++ b/syncope620/server/persistence-jpa/src/test/resources/persistenceTestEnv.xml
@@ -45,5 +45,10 @@ under the License.
   
   <context:component-scan base-package="org.apache.syncope.server.utils"/>
 
+  <bean id="virAttrCache" class="org.apache.syncope.provisioning.common.cache.MemoryVirAttrCache" scope="singleton">
+    <constructor-arg value="60"/>
+    <constructor-arg value="5000"/>
+  </bean>
+
   <import resource="persistenceContext.xml"/>
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/pom.xml b/syncope620/server/pom.xml
index fe6c089..19a959b 100644
--- a/syncope620/server/pom.xml
+++ b/syncope620/server/pom.xml
@@ -34,12 +34,15 @@ under the License.
   <packaging>pom</packaging>
 
   <modules>
-    <module>provisioning-api</module>
     <module>persistence-api</module>
     <module>persistence-jpa</module>
     <module>spring</module>
     <module>security</module>
     <module>utils</module>
+    <module>provisioning-api</module>
+    <module>provisioning-common</module>
+    <module>workflow-api</module>
+    <module>logic</module>
   </modules>
 
 </project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/AttributableTransformer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/AttributableTransformer.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/AttributableTransformer.java
new file mode 100644
index 0000000..b1f39f9
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/AttributableTransformer.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.provisioning.api;
+
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+
+/**
+ * Provides logic for transforming user or role, received as input by RESTful methods, before any internal
+ * processing logic takes place.
+ */
+public interface AttributableTransformer {
+
+    <T extends AbstractAttributableTO> T transform(T input);
+
+    <T extends AbstractAttributableMod> T transform(T input);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ConnIdBundleManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ConnIdBundleManager.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ConnIdBundleManager.java
new file mode 100644
index 0000000..bf07215
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ConnIdBundleManager.java
@@ -0,0 +1,284 @@
+/*
+ * 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.provisioning.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.identityconnectors.common.IOUtil;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.api.APIConfiguration;
+import org.identityconnectors.framework.api.ConfigurationProperties;
+import org.identityconnectors.framework.api.ConnectorInfo;
+import org.identityconnectors.framework.api.ConnectorInfoManager;
+import org.identityconnectors.framework.api.ConnectorInfoManagerFactory;
+import org.identityconnectors.framework.api.ConnectorKey;
+import org.identityconnectors.framework.api.RemoteFrameworkConnectionInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manage information about ConnId connector bundles.
+ */
+public class ConnIdBundleManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ConnIdBundleManager.class);
+
+    private String stringLocations;
+
+    /**
+     * ConnId Locations.
+     */
+    private List<URI> locations;
+
+    /**
+     * ConnectorInfoManager instances.
+     */
+    private final Map<URI, ConnectorInfoManager> connInfoManagers =
+            Collections.synchronizedMap(new LinkedHashMap<URI, ConnectorInfoManager>());
+
+    public void setStringLocations(final String stringLocations) {
+        this.stringLocations = stringLocations;
+    }
+
+    private void init() {
+        if (locations == null) {
+            locations = new ArrayList<>();
+            for (String location : StringUtils.isBlank(stringLocations) ? new String[0] : stringLocations.split(",")) {
+                try {
+                    locations.add(URIUtil.buildForConnId(location));
+                    LOG.info("Valid ConnId location: {}", location.trim());
+                } catch (Exception e) {
+                    LOG.error("Invalid ConnId location: {}", location.trim(), e);
+                }
+            }
+            locations = Collections.unmodifiableList(locations);
+        }
+    }
+
+    private void initLocal(final URI location) {
+        // 1. Find bundles inside local directory
+        File bundleDirectory = new File(location);
+        String[] bundleFiles = bundleDirectory.list();
+        if (bundleFiles == null) {
+            throw new NotFoundException("Local bundles directory " + location);
+        }
+
+        List<URL> bundleFileURLs = new ArrayList<>();
+        for (String file : bundleFiles) {
+            try {
+                bundleFileURLs.add(IOUtil.makeURL(bundleDirectory, file));
+            } catch (IOException ignore) {
+                // ignore exception and don't add bundle
+                LOG.debug("{}/{} is not a valid connector bundle", bundleDirectory.toString(), file, ignore);
+            }
+        }
+
+        if (bundleFileURLs.isEmpty()) {
+            LOG.warn("No connector bundles found in {}", location);
+        }
+        LOG.debug("Configuring local connector server:"
+                + "\n\tFiles: {}", bundleFileURLs);
+
+        // 2. Get connector info manager
+        ConnectorInfoManager manager = ConnectorInfoManagerFactory.getInstance().getLocalManager(
+                bundleFileURLs.toArray(new URL[bundleFileURLs.size()]));
+        if (manager == null) {
+            throw new NotFoundException("Local ConnectorInfoManager");
+        }
+
+        connInfoManagers.put(location, manager);
+    }
+
+    private void initRemote(final URI location) {
+        // 1. Extract conf params for remote connection from given URI
+        final String host = location.getHost();
+        final int port = location.getPort();
+        final GuardedString key = new GuardedString(location.getUserInfo().toCharArray());
+        final boolean useSSL = location.getScheme().equals("connids");
+
+        final List<TrustManager> trustManagers = new ArrayList<>();
+        final String[] params = StringUtils.isBlank(location.getQuery()) ? null : location.getQuery().split("&");
+        if (params != null && params.length > 0) {
+            final String[] trustAllCerts = params[0].split("=");
+            if (trustAllCerts != null && trustAllCerts.length > 1
+                    && "trustAllCerts".equalsIgnoreCase(trustAllCerts[0])
+                    && "true".equalsIgnoreCase(trustAllCerts[1])) {
+
+                trustManagers.add(new X509TrustManager() {
+
+                    @Override
+                    public void checkClientTrusted(final X509Certificate[] chain, final String authType)
+                            throws CertificateException {
+                        // no checks, trust all
+                    }
+
+                    @Override
+                    public void checkServerTrusted(final X509Certificate[] chain, final String authType)
+                            throws CertificateException {
+                        // no checks, trust all
+                    }
+
+                    @Override
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+                });
+            }
+        }
+
+        LOG.debug("Configuring remote connector server:"
+                + "\n\tHost: {}"
+                + "\n\tPort: {}"
+                + "\n\tKey: {}"
+                + "\n\tUseSSL: {}"
+                + "\n\tTrustAllCerts: {}",
+                host, port, key, useSSL, !trustManagers.isEmpty());
+
+        RemoteFrameworkConnectionInfo info =
+                new RemoteFrameworkConnectionInfo(host, port, key, useSSL, trustManagers, 60 * 1000);
+        LOG.debug("Remote connection info: {}", info);
+
+        // 2. Get connector info manager
+        ConnectorInfoManager manager = ConnectorInfoManagerFactory.getInstance().getRemoteManager(info);
+        if (manager == null) {
+            throw new NotFoundException("Remote ConnectorInfoManager");
+        }
+
+        connInfoManagers.put(location, manager);
+    }
+
+    public void resetConnManagers() {
+        connInfoManagers.clear();
+    }
+
+    public Map<URI, ConnectorInfoManager> getConnManagers() {
+        init();
+
+        if (connInfoManagers.isEmpty()) {
+            for (URI location : locations) {
+                try {
+                    if ("file".equals(location.getScheme())) {
+                        LOG.debug("Local initialization: {}", location);
+                        initLocal(location);
+                    } else if (location.getScheme().startsWith("connid")) {
+                        LOG.debug("Remote initialization: {}", location);
+                        initRemote(location);
+                    } else {
+                        LOG.warn("Unsupported scheme: {}", location);
+                    }
+                } catch (Exception e) {
+                    LOG.error("Could not process {}", location, e);
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            for (Map.Entry<URI, ConnectorInfoManager> entry : connInfoManagers.entrySet()) {
+                LOG.debug("Connector bundles found at {}", entry.getKey());
+                for (ConnectorInfo connInfo : entry.getValue().getConnectorInfos()) {
+                    LOG.debug("\t{}", connInfo.getConnectorDisplayName());
+                }
+            }
+        }
+
+        return connInfoManagers;
+    }
+
+    public ConnectorInfo getConnectorInfo(
+            final String location, final String bundleName, final String bundleVersion, final String connectorName) {
+
+        // check ConnIdLocation
+        URI uriLocation = null;
+        try {
+            uriLocation = URIUtil.buildForConnId(location);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Invalid ConnId location " + location, e);
+        }
+
+        // create key for search all properties
+        final ConnectorKey key = new ConnectorKey(bundleName, bundleVersion, connectorName);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("\nBundle name: " + key.getBundleName()
+                    + "\nBundle version: " + key.getBundleVersion()
+                    + "\nBundle class: " + key.getConnectorName());
+        }
+
+        // get the specified connector
+        ConnectorInfo info = null;
+        if (getConnManagers().containsKey(uriLocation)) {
+            info = getConnManagers().get(uriLocation).findConnectorInfo(key);
+        }
+        if (info == null) {
+            throw new NotFoundException("Connector Info for location " + location + " and key " + key);
+        }
+
+        return info;
+    }
+
+    public Map<String, List<ConnectorInfo>> getConnectorInfos() {
+        final Map<String, List<ConnectorInfo>> infos = new LinkedHashMap<>();
+        for (Map.Entry<URI, ConnectorInfoManager> entry : connInfoManagers.entrySet()) {
+            infos.put(entry.getKey().toString(), entry.getValue().getConnectorInfos());
+        }
+        return infos;
+    }
+
+    public ConfigurationProperties getConfigurationProperties(final ConnectorInfo info) {
+        if (info == null) {
+            throw new NotFoundException("Invalid: connector info is null");
+        }
+
+        // create default configuration
+        final APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
+        if (apiConfig == null) {
+            throw new NotFoundException("Default API configuration");
+        }
+
+        // retrieve the ConfigurationProperties.
+        final ConfigurationProperties properties = apiConfig.getConfigurationProperties();
+        if (properties == null) {
+            throw new NotFoundException("Configuration properties");
+        }
+
+        if (LOG.isDebugEnabled()) {
+            for (String propName : properties.getPropertyNames()) {
+                LOG.debug("Property Name: {}"
+                        + "\nProperty Type: {}",
+                        properties.getProperty(propName).getName(),
+                        properties.getProperty(propName).getType());
+            }
+        }
+
+        return properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ProvisioningManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ProvisioningManager.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ProvisioningManager.java
new file mode 100644
index 0000000..66f5e73
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/ProvisioningManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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.provisioning.api;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+
+public interface ProvisioningManager<T extends AbstractAttributableTO, M extends AbstractAttributableMod> {
+
+    Map.Entry<Long, List<PropagationStatus>> create(T subject);
+
+    Map.Entry<Long, List<PropagationStatus>> update(M subjectMod);
+
+    List<PropagationStatus> delete(Long subjectId);
+
+    Long unlink(M subjectMod);
+
+    Long link(M subjectMod);
+
+    List<PropagationStatus> deprovision(Long user, Collection<String> resources);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/RoleProvisioningManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/RoleProvisioningManager.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/RoleProvisioningManager.java
new file mode 100644
index 0000000..822981b
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/RoleProvisioningManager.java
@@ -0,0 +1,37 @@
+/*
+ * 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.provisioning.api;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+
+public interface RoleProvisioningManager extends ProvisioningManager<RoleTO, RoleMod> {
+
+    Map.Entry<Long, List<PropagationStatus>> create(RoleTO roleTO, Set<String> excludedResources);
+
+    Map.Entry<Long, List<PropagationStatus>> create(
+            RoleTO roleTO, Map<Long, String> roleOwnerMap, Set<String> excludedResources);
+
+    Map.Entry<Long, List<PropagationStatus>> update(RoleMod subjectMod, Set<String> excludedResources);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/URIUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/URIUtil.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/URIUtil.java
new file mode 100644
index 0000000..f8fd111
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/URIUtil.java
@@ -0,0 +1,61 @@
+/*
+ * 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.provisioning.api;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public final class URIUtil {
+
+    private URIUtil() {
+        // empty constructor for static utility class
+    }
+
+    /**
+     * Build a valid URI out of the given location.
+     * Only "file", "connid" and "connids" schemes are allowed.
+     * For "file", invalid characters are handled via intermediate transformation into URL.
+     *
+     * @param location the candidate location for URI
+     * @return valid URI for the given location
+     * @throws MalformedURLException if the intermediate URL is not valid
+     * @throws URISyntaxException if the given location does not correspond to a valid URI
+     */
+    public static URI buildForConnId(final String location) throws MalformedURLException, URISyntaxException {
+        final String candidate = location.trim();
+
+        if (!candidate.startsWith("file:")
+                && !candidate.startsWith("connid:") && !candidate.startsWith("connids:")) {
+
+            throw new IllegalArgumentException(candidate + " is not a valid URI for file or connid(s) schemes");
+        }
+
+        URI uri;
+        if (candidate.startsWith("file:")) {
+            uri = new File(new URL(candidate).getFile()).getAbsoluteFile().toURI();
+        } else {
+            uri = new URI(candidate);
+        }
+
+        return uri;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/UserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/UserProvisioningManager.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/UserProvisioningManager.java
new file mode 100644
index 0000000..b686127
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/UserProvisioningManager.java
@@ -0,0 +1,57 @@
+/*
+ * 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.provisioning.api;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.mod.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.sync.SyncResult;
+
+public interface UserProvisioningManager extends ProvisioningManager<UserTO, UserMod> {
+
+    Map.Entry<Long, List<PropagationStatus>> activate(User user, StatusMod statusMod);
+
+    Map.Entry<Long, List<PropagationStatus>> reactivate(User user, StatusMod statusMod);
+
+    Map.Entry<Long, List<PropagationStatus>> suspend(User user, StatusMod statusMod);
+
+    void innerSuspend(User user, boolean propagate);
+
+    Map.Entry<Long, List<PropagationStatus>> create(UserTO userTO, boolean storePassword);
+
+    Map.Entry<Long, List<PropagationStatus>> create(UserTO userTO, boolean storePassword,
+            boolean disablePwdPolicyCheck, Boolean enabled, Set<String> excludedResources);
+
+    Map.Entry<Long, List<PropagationStatus>> update(UserMod userMod, boolean removeMemberships);
+
+    Map.Entry<Long, List<PropagationStatus>> update(UserMod userMod, Long key,
+            SyncResult result, Boolean enabled, Set<String> excludedResources);
+
+    List<PropagationStatus> delete(Long subjectKey, Set<String> excludedResources);
+
+    void requestPasswordReset(Long key);
+
+    void confirmPasswordReset(User user, String token, String password);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/WorkflowResult.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/WorkflowResult.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/WorkflowResult.java
new file mode 100644
index 0000000..bb80219
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/WorkflowResult.java
@@ -0,0 +1,87 @@
+/*
+ * 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.provisioning.api;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+
+public class WorkflowResult<T> {
+
+    private T result;
+
+    private PropagationByResource propByRes;
+
+    private Set<String> performedTasks;
+
+    public WorkflowResult(final T result, final PropagationByResource propByRes, final String performedTask) {
+        this.result = result;
+        this.propByRes = propByRes;
+        this.performedTasks = Collections.singleton(performedTask);
+    }
+
+    public WorkflowResult(final T result, final PropagationByResource propByRes, final Set<String> performedTasks) {
+        this.result = result;
+        this.propByRes = propByRes;
+        this.performedTasks = performedTasks;
+    }
+
+    public T getResult() {
+        return result;
+    }
+
+    public void setResult(final T result) {
+        this.result = result;
+    }
+
+    public Set<String> getPerformedTasks() {
+        return performedTasks;
+    }
+
+    public void setPerformedTasks(final Set<String> performedTasks) {
+        this.performedTasks = performedTasks;
+    }
+
+    public PropagationByResource getPropByRes() {
+        return propByRes;
+    }
+
+    public void setPropByRes(final PropagationByResource propByRes) {
+        this.propByRes = propByRes;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCache.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCache.java
new file mode 100644
index 0000000..c4936a5
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCache.java
@@ -0,0 +1,65 @@
+/*
+ * 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.provisioning.api.cache;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+
+/**
+ * Virtual Attribute Value cache.
+ */
+public interface VirAttrCache {
+
+    /**
+     * Force entry expiring.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name
+     */
+    void expire(AttributableType type, Long id, String schemaName);
+
+    /**
+     * Retrieve cached value. Return null in case of virtual attribute not cached.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name.
+     * @return cached values or null if virtual attribute is not cached.
+     */
+    VirAttrCacheValue get(AttributableType type, Long id, String schemaName);
+
+    /**
+     * Cache entry is valid if and only if value exist and it is not expired.
+     *
+     * @param value cache entry value.
+     * @return TRUE if the value is valid; FALSE otherwise.
+     */
+    boolean isValidEntry(VirAttrCacheValue value);
+
+    /**
+     * Cache virtual attribute values.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute name
+     * @param value virtual attribute values
+     */
+    void put(AttributableType type, Long id, String schemaName, VirAttrCacheValue value);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheKey.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheKey.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheKey.java
new file mode 100644
index 0000000..bdc60db
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheKey.java
@@ -0,0 +1,79 @@
+/*
+ * 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.provisioning.api.cache;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.types.AttributableType;
+
+/**
+ * Cache entry key.
+ */
+public class VirAttrCacheKey {
+
+    /**
+     * Subject type.
+     */
+    private final AttributableType type;
+
+    /**
+     * Subject ID.
+     */
+    private final transient Long id;
+
+    /**
+     * Virtual attribute schema name.
+     */
+    private final transient String virSchema;
+
+    public VirAttrCacheKey(final AttributableType type, final Long id, final String virSchema) {
+        this.type = type;
+        this.id = id;
+        this.virSchema = virSchema;
+    }
+
+    public AttributableType getType() {
+        return type;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getVirSchema() {
+        return virSchema;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj, true);
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this, true);
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE, true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheValue.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheValue.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheValue.java
new file mode 100644
index 0000000..1ca9965
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/cache/VirAttrCacheValue.java
@@ -0,0 +1,86 @@
+/*
+ * 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.provisioning.api.cache;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Cache entry value.
+ */
+public class VirAttrCacheValue {
+
+    /**
+     * Virtual attribute values.
+     */
+    private final Map<String, Set<String>> values;
+
+    /**
+     * Entry creation date.
+     */
+    private Date creationDate;
+
+    /**
+     * Entry access date.
+     */
+    private Date lastAccessDate;
+
+    public VirAttrCacheValue() {
+        this.creationDate = new Date();
+        this.lastAccessDate = new Date();
+        values = new HashMap<>();
+    }
+
+    public void setResourceValues(final String resourceName, final Set<String> values) {
+        this.values.put(resourceName, values);
+    }
+
+    public Date getCreationDate() {
+        return creationDate;
+    }
+
+    public void forceExpiring() {
+        creationDate = new Date(0);
+    }
+
+    public Set<String> getValues(final String resourceName) {
+        return values.get(resourceName);
+    }
+
+    public Set<String> getValues() {
+        final Set<String> res = new HashSet<>();
+
+        for (Set<String> value : values.values()) {
+            res.addAll(value);
+        }
+
+        return res;
+    }
+
+    public Date getLastAccessDate() {
+        return lastAccessDate;
+    }
+
+    void setLastAccessDate(final Date lastAccessDate) {
+        this.lastAccessDate = lastAccessDate;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationByResource.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationByResource.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationByResource.java
new file mode 100644
index 0000000..e43443e
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationByResource.java
@@ -0,0 +1,365 @@
+/*
+ * 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.provisioning.api.propagation;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+
+/**
+ * Utility class for encapsulating operations to be performed on various resources.
+ */
+public class PropagationByResource implements Serializable {
+
+    private static final long serialVersionUID = -5699740428104336636L;
+
+    /**
+     * Resources for creation.
+     */
+    private final Set<String> toBeCreated;
+
+    /**
+     * Resources for update.
+     */
+    private final Set<String> toBeUpdated;
+
+    /**
+     * Resources for deletion.
+     */
+    private final Set<String> toBeDeleted;
+
+    /**
+     * Mapping target resource names to old account ids (when applicable).
+     */
+    private final Map<String, String> oldAccountIds;
+
+    /**
+     * Default constructor.
+     */
+    public PropagationByResource() {
+        toBeCreated = new HashSet<String>();
+        toBeUpdated = new HashSet<String>();
+        toBeDeleted = new HashSet<String>();
+
+        oldAccountIds = new HashMap<String, String>();
+    }
+
+    /**
+     * Avoid potential conflicts by not doing create or update on any resource for which a delete is requested, and by
+     * not doing any create on any resource for which an update is requested.
+     */
+    public final void purge() {
+        toBeCreated.removeAll(toBeDeleted);
+        toBeCreated.removeAll(toBeUpdated);
+
+        toBeUpdated.removeAll(toBeDeleted);
+    }
+
+    /**
+     * Add an element.
+     *
+     * @param type resource operation type
+     * @param resourceName target resource
+     * @return whether the operation was successful or not
+     */
+    public final boolean add(final ResourceOperation type, final String resourceName) {
+        Set<String> set;
+        switch (type) {
+            case CREATE:
+                set = toBeCreated;
+                break;
+
+            case UPDATE:
+                set = toBeUpdated;
+                break;
+
+            case DELETE:
+            default:
+                set = toBeDeleted;
+                break;
+        }
+
+        return set.add(resourceName);
+    }
+
+    /**
+     * Add some elements.
+     *
+     * @param type resource operation type
+     * @param resourceNames target resources
+     * @return whether the operation was successful or not
+     */
+    public boolean addAll(final ResourceOperation type, final Collection<String> resourceNames) {
+        Set<String> set;
+        switch (type) {
+            case CREATE:
+                set = toBeCreated;
+                break;
+
+            case UPDATE:
+                set = toBeUpdated;
+                break;
+
+            case DELETE:
+            default:
+                set = toBeDeleted;
+                break;
+        }
+
+        return set.addAll(resourceNames);
+    }
+
+    /**
+     * Remove an element.
+     *
+     * @param type resource operation type
+     * @param resourceName target resource
+     * @return whether the operation was successful or not
+     */
+    public final boolean remove(final ResourceOperation type, final String resourceName) {
+        boolean result = false;
+
+        switch (type) {
+            case CREATE:
+                result = toBeCreated.remove(resourceName);
+                break;
+
+            case UPDATE:
+                result = toBeUpdated.remove(resourceName);
+                break;
+
+            case DELETE:
+                result = toBeDeleted.remove(resourceName);
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    /**
+     * Remove some elements.
+     *
+     * @param type resource operation type
+     * @param resourceNames target resources
+     * @return whether the operation was successful or not
+     */
+    public boolean removeAll(final ResourceOperation type, final Set<String> resourceNames) {
+        Set<String> set;
+        switch (type) {
+            case CREATE:
+                set = toBeCreated;
+                break;
+
+            case UPDATE:
+                set = toBeUpdated;
+                break;
+
+            case DELETE:
+            default:
+                set = toBeDeleted;
+                break;
+        }
+
+        return set.removeAll(resourceNames);
+    }
+
+    /**
+     * Removes only the resource names in the underlying resource name sets that are contained in the specified
+     * collection.
+     *
+     * @param resourceNames collection containing resource names to be retained in the underlying resource name sets
+     * @return <tt>true</tt> if the underlying resource name sets changed as a result of the call
+     * @see Collection#removeAll(java.util.Collection)
+     */
+    public boolean removeAll(final Collection<String> resourceNames) {
+        return toBeCreated.removeAll(resourceNames)
+                | toBeUpdated.removeAll(resourceNames)
+                | toBeDeleted.removeAll(resourceNames);
+    }
+
+    /**
+     * Retains only the resource names in the underlying resource name sets that are contained in the specified
+     * collection.
+     *
+     * @param resourceNames collection containing resource names to be retained in the underlying resource name sets
+     * @return <tt>true</tt> if the underlying resource name sets changed as a result of the call
+     * @see Collection#retainAll(java.util.Collection)
+     */
+    public boolean retainAll(final Collection<String> resourceNames) {
+        return toBeCreated.retainAll(resourceNames)
+                | toBeUpdated.retainAll(resourceNames)
+                | toBeDeleted.retainAll(resourceNames);
+    }
+
+    public boolean contains(final ResourceOperation type, final String resourceName) {
+        boolean result = false;
+
+        switch (type) {
+            case CREATE:
+                result = toBeCreated.contains(resourceName);
+                break;
+
+            case UPDATE:
+                result = toBeUpdated.contains(resourceName);
+                break;
+
+            case DELETE:
+                result = toBeDeleted.contains(resourceName);
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    /**
+     * Get resources for a given resource operation type.
+     *
+     * @param type resource operation type
+     * @return resource matching the given type
+     */
+    public final Set<String> get(final ResourceOperation type) {
+        Set<String> result = Collections.<String>emptySet();
+
+        switch (type) {
+            case CREATE:
+                result = toBeCreated;
+                break;
+
+            case UPDATE:
+                result = toBeUpdated;
+                break;
+
+            case DELETE:
+                result = toBeDeleted;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    /**
+     * Set resources for a given resource operation type.
+     *
+     * @param type resource operation type
+     * @param resourceNames to be set
+     */
+    public final void set(final ResourceOperation type, final Set<String> resourceNames) {
+
+        switch (type) {
+            case CREATE:
+                toBeCreated.clear();
+                toBeCreated.addAll(resourceNames);
+                break;
+
+            case UPDATE:
+                toBeUpdated.clear();
+                toBeUpdated.addAll(resourceNames);
+                break;
+
+            case DELETE:
+                toBeDeleted.clear();
+                toBeDeleted.addAll(resourceNames);
+                break;
+
+            default:
+        }
+    }
+
+    /**
+     * Merge another resource operation instance into this instance.
+     *
+     * @param propByRes to be merged
+     */
+    public final void merge(final PropagationByResource propByRes) {
+        if (propByRes != null) {
+            toBeCreated.addAll(propByRes.get(ResourceOperation.CREATE));
+            toBeUpdated.addAll(propByRes.get(ResourceOperation.UPDATE));
+            toBeDeleted.addAll(propByRes.get(ResourceOperation.DELETE));
+            oldAccountIds.putAll(propByRes.getOldAccountIds());
+        }
+    }
+
+    /**
+     * Removes all of the operations.
+     */
+    public void clear() {
+        toBeCreated.clear();
+        toBeUpdated.clear();
+        toBeDeleted.clear();
+    }
+
+    /**
+     * Whether no operations are present.
+     *
+     * @return true if no operations (create / update / delete) and no old account ids are present
+     */
+    public final boolean isEmpty() {
+        return toBeCreated.isEmpty() && toBeUpdated.isEmpty() && toBeDeleted.isEmpty() && oldAccountIds.isEmpty();
+    }
+
+    /**
+     * Fetch all old account ids.
+     *
+     * @return old account ids; can be empty
+     */
+    public Map<String, String> getOldAccountIds() {
+        return oldAccountIds;
+    }
+
+    /**
+     * Fetch old account id for given resource name.
+     *
+     * @param resourceName resource name
+     * @return old account id; can be null
+     */
+    public String getOldAccountId(final String resourceName) {
+        return oldAccountIds.get(resourceName);
+    }
+
+    /**
+     * Add old account id for a given resource name.
+     *
+     * @param resourceName resourceName resource name
+     * @param oldAccountId old account id
+     */
+    public void addOldAccountId(final String resourceName, final String oldAccountId) {
+        if (resourceName != null && oldAccountId != null) {
+            oldAccountIds.put(resourceName, oldAccountId);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "To be Created: " + toBeCreated + ";\n"
+                + "To be Updated: " + toBeUpdated + ";\n"
+                + "To be Deleted: " + toBeDeleted + ";\n"
+                + "Old account Ids: " + oldAccountIds;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationException.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationException.java
new file mode 100644
index 0000000..ff25634
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationException.java
@@ -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.
+ */
+package org.apache.syncope.provisioning.api.propagation;
+
+/**
+ * Bear stacktrace received during propagation towards a certain resource.
+ */
+public class PropagationException extends RuntimeException {
+
+    private static final long serialVersionUID = -4828426289616526116L;
+
+    /**
+     * The resource involved in this exception.
+     */
+    private final String resourceName;
+
+    /**
+     * Create a new instance based on resource name and original stacktrace received during propagation.
+     *
+     * @param resourceName name of resource involved in this exception
+     * @param stackTrace original stacktrace
+     */
+    public PropagationException(final String resourceName, final String stackTrace) {
+        super("Exception during provision on resource " + resourceName + "\n" + stackTrace);
+
+        this.resourceName = resourceName;
+    }
+
+    /**
+     * @return name of resource involved in this exception
+     */
+    public String getResourceName() {
+        return resourceName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationManager.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationManager.java
new file mode 100644
index 0000000..d2060c9
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationManager.java
@@ -0,0 +1,248 @@
+/*
+ * 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.provisioning.api.propagation;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.WorkflowResult;
+
+public interface PropagationManager {
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param vAttrs virtual attributes to be set
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleCreateTaskIds(WorkflowResult<Long> wfResult, List<AttrTO> vAttrs);
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrs virtual attributes to be set
+     * @param noPropResourceNames external resources performing not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleCreateTaskIds(
+            WorkflowResult<Long> wfResult, Collection<AttrTO> vAttrs, Collection<String> noPropResourceNames);
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param key role id
+     * @param vAttrs virtual attributes to be set
+     * @param propByRes operation to be performed per resource
+     * @param noPropResourceNames external resources performing not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleCreateTaskIds(Long key, Collection<AttrTO> vAttrs, PropagationByResource propByRes,
+            Collection<String> noPropResourceNames);
+
+    /**
+     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleId to be deleted
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleDeleteTaskIds(Long roleId);
+
+    /**
+     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleId to be deleted
+     * @param noPropResourceName name of external resource not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleDeleteTaskIds(Long roleId, String noPropResourceName);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleId to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleDeleteTaskIds(Long roleId, Collection<String> noPropResourceNames);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleId to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleDeleteTaskIds(
+            Long roleId, Set<String> resourceNames, Collection<String> noPropResourceNames);
+
+    /**
+     * Performs update on each resource associated to the role.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleUpdateTaskIds(WorkflowResult<Long> wfResult, Set<String> vAttrsToBeRemoved,
+            Set<AttrMod> vAttrsToBeUpdated);
+
+    /**
+     * Performs update on each resource associated to the role.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param noPropResourceNames external resource names not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getRoleUpdateTaskIds(WorkflowResult<Long> wfResult, Set<String> vAttrsToBeRemoved,
+            Set<AttrMod> vAttrsToBeUpdated, Set<String> noPropResourceNames);
+
+    List<PropagationTask> getUpdateTaskIds(Subject<?, ?, ?> subject, String password, boolean changePwd,
+            Boolean enable, Set<String> vAttrsToBeRemoved, Set<AttrMod> vAttrsToBeUpdated,
+            PropagationByResource propByRes, Collection<String> noPropResourceNames,
+            Set<MembershipMod> membershipsToAdd);
+
+    /**
+     * Create the user on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param password to be set
+     * @param vAttrs virtual attributes to be set
+     * @param membershipTOs user memberships
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserCreateTaskIds(WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
+            String password, List<AttrTO> vAttrs, List<MembershipTO> membershipTOs);
+
+    /**
+     * Create the user on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param password to be set
+     * @param vAttrs virtual attributes to be set
+     * @param noPropResourceNames external resources not to be considered for propagation
+     * @param membershipTOs user memberships
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserCreateTaskIds(WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
+            String password, Collection<AttrTO> vAttrs, Set<String> noPropResourceNames,
+            List<MembershipTO> membershipTOs);
+
+    List<PropagationTask> getUserCreateTaskIds(Long id, Boolean enabled,
+            PropagationByResource propByRes, String password, Collection<AttrTO> vAttrs,
+            Collection<MembershipTO> membershipTOs, Collection<String> noPropResourceNames);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserDeleteTaskIds(Long userKey);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param noPropResourceName name of external resource not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserDeleteTaskIds(Long userKey, String noPropResourceName);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserDeleteTaskIds(Long userKey, Collection<String> noPropResourceNames);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserDeleteTaskIds(
+            Long userKey, Set<String> resourceNames, Collection<String> noPropResourceNames);
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserDeleteTaskIds(WorkflowResult<Long> wfResult);
+
+    /**
+     * Performs update on each resource associated to the user excluding the specified into 'resourceNames' parameter.
+     *
+     * @param user to be propagated
+     * @param enable whether user must be enabled or not
+     * @param noPropResourceNames external resource names not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserUpdateTaskIds(User user, Boolean enable, Set<String> noPropResourceNames);
+
+    /**
+     * Performs update on each resource associated to the user.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param changePwd whether password should be included for propagation attributes or not
+     * @param noPropResourceNames external resources not to be considered for propagation
+     * @return list of propagation tasks
+     */
+    List<PropagationTask> getUserUpdateTaskIds(WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult,
+            boolean changePwd, Collection<String> noPropResourceNames);
+
+    List<PropagationTask> getUserUpdateTaskIds(WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationReporter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationReporter.java b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationReporter.java
new file mode 100644
index 0000000..b4b714b
--- /dev/null
+++ b/syncope620/server/provisioning-api/src/main/java/org/apache/syncope/provisioning/api/propagation/PropagationReporter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.provisioning.api.propagation;
+
+import java.util.List;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+
+/**
+ * Report propagation status after executions.
+ */
+public interface PropagationReporter {
+
+    /**
+     * Report propagation status after executions in case of success or non-blocking failure
+     * (e.g. on secondary resources).
+     *
+     * @param resourceName resource name.
+     * @param execStatus propagation execution status.
+     * @param failureReason propagation execution failure message.
+     * @param beforeObj retrieved connector object before operation execution.
+     * @param afterObj retrieved connector object after operation execution.
+     */
+    void onSuccessOrSecondaryResourceFailures(String resourceName, PropagationTaskExecStatus execStatus,
+            String failureReason, ConnectorObject beforeObj, ConnectorObject afterObj);
+
+    /**
+     * Report propagation status after executions in case blocking failure (e.g. on primary resources).
+     *
+     * @param tasks propagation tasks performed before failure
+     */
+    void onPrimaryResourceFailure(List<PropagationTask> tasks);
+
+    /**
+     * Returns the list of propagation statuses.
+     *
+     * @return the list of propagation statuses
+     */
+    List<PropagationStatus> getStatuses();
+}


[06/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/TaskDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/TaskDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/TaskDataBinder.java
new file mode 100644
index 0000000..204207b
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/TaskDataBinder.java
@@ -0,0 +1,338 @@
+/*
+ * 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.server.logic.data;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractProvisioningTaskTO;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.TaskExecTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.persistence.api.entity.task.PushTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.persistence.api.entity.task.TaskUtil;
+import org.apache.syncope.server.logic.init.JobInstanceLoader;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TaskDataBinder {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(TaskDataBinder.class);
+
+    private static final String[] IGNORE_TASK_PROPERTIES = {
+        "executions", "resource", "matchingRule", "unmatchingRule" };
+
+    private static final String[] IGNORE_TASK_EXECUTION_PROPERTIES = { "key", "task" };
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private TaskExecDAO taskExecDAO;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    private void checkJexl(final AbstractAttributableTO attributableTO, final SyncopeClientException sce) {
+        for (AttrTO attrTO : attributableTO.getPlainAttrs()) {
+            if (!attrTO.getValues().isEmpty() && !JexlUtil.isExpressionValid(attrTO.getValues().get(0))) {
+                sce.getElements().add("Invalid JEXL: " + attrTO.getValues().get(0));
+            }
+        }
+
+        for (AttrTO attrTO : attributableTO.getVirAttrs()) {
+            if (!attrTO.getValues().isEmpty() && !JexlUtil.isExpressionValid(attrTO.getValues().get(0))) {
+                sce.getElements().add("Invalid JEXL: " + attrTO.getValues().get(0));
+            }
+        }
+    }
+
+    private void fill(final ProvisioningTask task, final AbstractProvisioningTaskTO taskTO) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSyncTask);
+
+        if (task instanceof PushTask && taskTO instanceof PushTaskTO) {
+            final PushTask pushTask = (PushTask) task;
+            final PushTaskTO pushTaskTO = (PushTaskTO) taskTO;
+
+            pushTask.setUserFilter(pushTaskTO.getUserFilter());
+            pushTask.setRoleFilter(pushTaskTO.getRoleFilter());
+
+            pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
+                    ? MatchingRule.LINK : pushTaskTO.getMatchingRule());
+
+            pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule() == null
+                    ? UnmatchingRule.ASSIGN : pushTaskTO.getUnmatchingRule());
+
+        } else if (task instanceof SyncTask && taskTO instanceof SyncTaskTO) {
+            final SyncTask syncTask = (SyncTask) task;
+            final SyncTaskTO syncTaskTO = (SyncTaskTO) taskTO;
+
+            syncTask.setMatchingRule(syncTaskTO.getMatchingRule() == null
+                    ? MatchingRule.UPDATE : syncTaskTO.getMatchingRule());
+
+            syncTask.setUnmatchingRule(syncTaskTO.getUnmatchingRule() == null
+                    ? UnmatchingRule.PROVISION : syncTaskTO.getUnmatchingRule());
+
+            // 1. validate JEXL expressions in user and role templates
+            if (syncTaskTO.getUserTemplate() != null) {
+                UserTO template = syncTaskTO.getUserTemplate();
+
+                if (StringUtils.isNotBlank(template.getUsername())
+                        && !JexlUtil.isExpressionValid(template.getUsername())) {
+
+                    sce.getElements().add("Invalid JEXL: " + template.getUsername());
+                }
+                if (StringUtils.isNotBlank(template.getPassword())
+                        && !JexlUtil.isExpressionValid(template.getPassword())) {
+
+                    sce.getElements().add("Invalid JEXL: " + template.getPassword());
+                }
+
+                checkJexl(template, sce);
+
+                for (MembershipTO memb : template.getMemberships()) {
+                    checkJexl(memb, sce);
+                }
+            }
+            if (syncTaskTO.getRoleTemplate() != null) {
+                RoleTO template = syncTaskTO.getRoleTemplate();
+
+                if (StringUtils.isNotBlank(template.getName()) && !JexlUtil.isExpressionValid(template.getName())) {
+                    sce.getElements().add("Invalid JEXL: " + template.getName());
+                }
+
+                checkJexl(template, sce);
+            }
+            if (!sce.isEmpty()) {
+                throw sce;
+            }
+
+            // 2. all JEXL expressions are valid: accept user and role templates
+            syncTask.setUserTemplate(syncTaskTO.getUserTemplate());
+            syncTask.setRoleTemplate(syncTaskTO.getRoleTemplate());
+
+            syncTask.setFullReconciliation(syncTaskTO.isFullReconciliation());
+        }
+
+        // 3. fill the remaining fields
+        task.setPerformCreate(taskTO.isPerformCreate());
+        task.setPerformUpdate(taskTO.isPerformUpdate());
+        task.setPerformDelete(taskTO.isPerformDelete());
+        task.setSyncStatus(taskTO.isSyncStatus());
+        task.getActionsClassNames().clear();
+        task.getActionsClassNames().addAll(taskTO.getActionsClassNames());
+    }
+
+    public SchedTask createSchedTask(final SchedTaskTO taskTO, final TaskUtil taskUtil) {
+        final Class<? extends AbstractTaskTO> taskTOClass = taskUtil.taskTOClass();
+
+        if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskTOClass, taskTO.getClass()));
+        }
+
+        SchedTask task = taskUtil.newTask();
+        task.setCronExpression(taskTO.getCronExpression());
+        task.setName(taskTO.getName());
+        task.setDescription(taskTO.getDescription());
+
+        if (taskUtil.getType() == TaskType.SCHEDULED) {
+            task.setJobClassName(taskTO.getJobClassName());
+        } else if (taskTO instanceof AbstractProvisioningTaskTO) {
+            final AbstractProvisioningTaskTO provisioningTaskTO = (AbstractProvisioningTaskTO) taskTO;
+
+            ExternalResource resource = resourceDAO.find(provisioningTaskTO.getResource());
+            if (resource == null) {
+                throw new NotFoundException("Resource " + provisioningTaskTO.getResource());
+            }
+            ((ProvisioningTask) task).setResource(resource);
+
+            fill((ProvisioningTask) task, provisioningTaskTO);
+        }
+
+        return task;
+    }
+
+    public void updateSchedTask(final SchedTask task, final SchedTaskTO taskTO, final TaskUtil taskUtil) {
+        Class<? extends Task> taskClass = taskUtil.taskClass();
+        Class<? extends AbstractTaskTO> taskTOClass = taskUtil.taskTOClass();
+
+        if (taskClass == null || !taskClass.equals(task.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskClass, task.getClass()));
+        }
+
+        if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskTOClass, taskTO.getClass()));
+        }
+
+        task.setCronExpression(taskTO.getCronExpression());
+        if (StringUtils.isNotBlank(taskTO.getName())) {
+            task.setName(taskTO.getName());
+        }
+        if (StringUtils.isNotBlank(taskTO.getDescription())) {
+            task.setDescription(taskTO.getDescription());
+        }
+
+        if (task instanceof ProvisioningTask) {
+            fill((ProvisioningTask) task, (AbstractProvisioningTaskTO) taskTO);
+        }
+    }
+
+    public TaskExecTO getTaskExecTO(final TaskExec execution) {
+        TaskExecTO executionTO = new TaskExecTO();
+        BeanUtils.copyProperties(execution, executionTO, IGNORE_TASK_EXECUTION_PROPERTIES);
+
+        if (execution.getKey() != null) {
+            executionTO.setKey(execution.getKey());
+        }
+
+        if (execution.getTask() != null && execution.getTask().getKey() != null) {
+            executionTO.setTask(execution.getTask().getKey());
+        }
+
+        return executionTO;
+    }
+
+    private void setExecTime(final SchedTaskTO taskTO, final Task task) {
+        String triggerName = JobInstanceLoader.getTriggerName(JobInstanceLoader.getJobName(task));
+
+        Trigger trigger = null;
+        try {
+            trigger = scheduler.getScheduler().getTrigger(new TriggerKey(triggerName, Scheduler.DEFAULT_GROUP));
+        } catch (SchedulerException e) {
+            LOG.warn("While trying to get to " + triggerName, e);
+        }
+
+        if (trigger != null) {
+            taskTO.setLastExec(trigger.getPreviousFireTime());
+            taskTO.setNextExec(trigger.getNextFireTime());
+        }
+    }
+
+    public <T extends AbstractTaskTO> T getTaskTO(final Task task, final TaskUtil taskUtil) {
+        T taskTO = taskUtil.newTaskTO();
+        BeanUtils.copyProperties(task, taskTO, IGNORE_TASK_PROPERTIES);
+
+        TaskExec latestExec = taskExecDAO.findLatestStarted(task);
+        taskTO.setLatestExecStatus(latestExec == null ? "" : latestExec.getStatus());
+        taskTO.setStartDate(latestExec == null ? null : latestExec.getStartDate());
+        taskTO.setEndDate(latestExec == null ? null : latestExec.getEndDate());
+
+        for (TaskExec execution : task.getExecs()) {
+            taskTO.getExecutions().add(getTaskExecTO(execution));
+        }
+
+        switch (taskUtil.getType()) {
+            case PROPAGATION:
+                if (!(task instanceof PropagationTask)) {
+                    throw new ClassCastException("taskUtil is type Propagation but task is not PropagationTask: "
+                            + task.getClass().getName());
+                }
+                ((PropagationTaskTO) taskTO).setResource(((PropagationTask) task).getResource().getKey());
+                break;
+
+            case SCHEDULED:
+                if (!(task instanceof SchedTask)) {
+                    throw new ClassCastException("taskUtil is type Sched but task is not SchedTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((SchedTaskTO) taskTO).setName(((SchedTask) task).getName());
+                ((SchedTaskTO) taskTO).setDescription(((SchedTask) task).getDescription());
+                break;
+
+            case SYNCHRONIZATION:
+                if (!(task instanceof SyncTask)) {
+                    throw new ClassCastException("taskUtil is type Sync but task is not SyncTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((SyncTaskTO) taskTO).setName(((SyncTask) task).getName());
+                ((SyncTaskTO) taskTO).setDescription(((SyncTask) task).getDescription());
+                ((SyncTaskTO) taskTO).setResource(((SyncTask) task).getResource().getKey());
+                ((SyncTaskTO) taskTO).setMatchingRule(((SyncTask) task).getMatchingRule() == null
+                        ? MatchingRule.UPDATE : ((SyncTask) task).getMatchingRule());
+                ((SyncTaskTO) taskTO).setUnmatchingRule(((SyncTask) task).getUnmatchingRule() == null
+                        ? UnmatchingRule.PROVISION : ((SyncTask) task).getUnmatchingRule());
+                break;
+
+            case PUSH:
+                if (!(task instanceof PushTask)) {
+                    throw new ClassCastException("taskUtil is type Push but task is not PushTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((PushTaskTO) taskTO).setName(((PushTask) task).getName());
+                ((PushTaskTO) taskTO).setDescription(((PushTask) task).getDescription());
+                ((PushTaskTO) taskTO).setResource(((PushTask) task).getResource().getKey());
+                ((PushTaskTO) taskTO).setMatchingRule(((PushTask) task).getMatchingRule() == null
+                        ? MatchingRule.LINK : ((PushTask) task).getMatchingRule());
+                ((PushTaskTO) taskTO).setUnmatchingRule(((PushTask) task).getUnmatchingRule() == null
+                        ? UnmatchingRule.ASSIGN : ((PushTask) task).getUnmatchingRule());
+                break;
+
+            case NOTIFICATION:
+                if (((NotificationTask) task).isExecuted() && StringUtils.isBlank(taskTO.getLatestExecStatus())) {
+                    taskTO.setLatestExecStatus("[EXECUTED]");
+                }
+                break;
+
+            default:
+        }
+
+        return taskTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/UserDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/UserDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/UserDataBinder.java
new file mode 100644
index 0000000..861452c
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/UserDataBinder.java
@@ -0,0 +1,479 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.SecurityQuestionDAO;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.apache.syncope.server.security.Encryptor;
+import org.apache.syncope.server.spring.BeanUtils;
+import org.apache.syncope.server.utils.ConnObjectUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional(rollbackFor = { Throwable.class })
+public class UserDataBinder extends AbstractAttributableDataBinder {
+
+    private static final String[] IGNORE_USER_PROPERTIES = {
+        "memberships", "plainAttrs", "derAttrs", "virAttrs", "resources", "securityQuestion", "securityAnswer"
+    };
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private SecurityQuestionDAO securityQuestionDAO;
+
+    @Resource(name = "adminUser")
+    private String adminUser;
+
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    private final Encryptor encryptor = Encryptor.getInstance();
+
+    @Transactional(readOnly = true)
+    public Membership getMembershipFromId(final Long membershipId) {
+        if (membershipId == null) {
+            throw new NotFoundException("Null membership id");
+        }
+
+        Membership membership = membershipDAO.find(membershipId);
+        if (membership == null) {
+            throw new NotFoundException("Membership " + membershipId);
+        }
+
+        return membership;
+    }
+
+    @Transactional(readOnly = true)
+    public Set<String> getResourceNamesForUser(final Long key) {
+        return userDAO.authFecthUser(key).getResourceNames();
+    }
+
+    @Transactional(readOnly = true)
+    public UserTO getAuthenticatedUserTO() {
+        final UserTO authUserTO;
+
+        final String authUsername = AuthContextUtil.getAuthenticatedUsername();
+        if (anonymousUser.equals(authUsername)) {
+            authUserTO = new UserTO();
+            authUserTO.setKey(-2);
+            authUserTO.setUsername(anonymousUser);
+        } else if (adminUser.equals(authUsername)) {
+            authUserTO = new UserTO();
+            authUserTO.setKey(-1);
+            authUserTO.setUsername(adminUser);
+        } else {
+            User authUser = userDAO.find(authUsername);
+            authUserTO = getUserTO(authUser);
+        }
+
+        return authUserTO;
+    }
+
+    @Transactional(readOnly = true)
+    public boolean verifyPassword(final String username, final String password) {
+        return verifyPassword(userDAO.authFecthUser(username), password);
+    }
+
+    @Transactional(readOnly = true)
+    public boolean verifyPassword(final User user, final String password) {
+        return encryptor.verify(password, user.getCipherAlgorithm(), user.getPassword());
+    }
+
+    private void setPassword(final User user, final String password,
+            final SyncopeClientCompositeException scce) {
+
+        try {
+            final String algorithm = confDAO.find(
+                    "password.cipher.algorithm", CipherAlgorithm.AES.name()).getValues().get(0).getStringValue();
+            CipherAlgorithm predefined = CipherAlgorithm.valueOf(algorithm);
+            user.setPassword(password, predefined);
+        } catch (IllegalArgumentException e) {
+            final SyncopeClientException invalidCiperAlgorithm =
+                    SyncopeClientException.build(ClientExceptionType.NotFound);
+            invalidCiperAlgorithm.getElements().add(e.getMessage());
+            scce.addException(invalidCiperAlgorithm);
+
+            throw scce;
+        }
+    }
+
+    public void create(final User user, final UserTO userTO, final boolean storePassword) {
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        // memberships
+        Role role;
+        for (MembershipTO membershipTO : userTO.getMemberships()) {
+            role = roleDAO.find(membershipTO.getRoleId());
+
+            if (role == null) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Ignoring invalid role " + membershipTO.getRoleName());
+                }
+            } else {
+                Membership membership = null;
+                if (user.getKey() != null) {
+                    membership = user.getMembership(role.getKey()) == null
+                            ? membershipDAO.find(user, role)
+                            : user.getMembership(role.getKey());
+                }
+                if (membership == null) {
+                    membership = entityFactory.newEntity(Membership.class);
+                    membership.setRole(role);
+                    membership.setUser(user);
+
+                    user.addMembership(membership);
+                }
+
+                fill(membership, membershipTO, attrUtilFactory.getInstance(AttributableType.MEMBERSHIP), scce);
+            }
+        }
+
+        // attributes, derived attributes, virtual attributes and resources
+        fill(user, userTO, attrUtilFactory.getInstance(AttributableType.USER), scce);
+
+        // set password
+        if (StringUtils.isBlank(userTO.getPassword()) || !storePassword) {
+            LOG.debug("Password was not provided or not required to be stored");
+        } else {
+            setPassword(user, userTO.getPassword(), scce);
+        }
+
+        // set username
+        user.setUsername(userTO.getUsername());
+
+        // security question / answer
+        if (userTO.getSecurityQuestion() != null) {
+            SecurityQuestion securityQuestion = securityQuestionDAO.find(userTO.getSecurityQuestion());
+            if (securityQuestion != null) {
+                user.setSecurityQuestion(securityQuestion);
+            }
+        }
+        user.setSecurityAnswer(userTO.getSecurityAnswer());
+    }
+
+    /**
+     * Update user, given UserMod.
+     *
+     * @param toBeUpdated user to be updated
+     * @param userMod bean containing update request
+     * @return updated user + propagation by resource
+     * @see PropagationByResource
+     */
+    public PropagationByResource update(final User toBeUpdated, final UserMod userMod) {
+        // Re-merge any pending change from workflow tasks
+        User user = userDAO.save(toBeUpdated);
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        Set<String> currentResources = user.getResourceNames();
+
+        // password
+        if (StringUtils.isNotBlank(userMod.getPassword())) {
+            setPassword(user, userMod.getPassword(), scce);
+            user.setChangePwdDate(new Date());
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+        }
+
+        // username
+        if (userMod.getUsername() != null && !userMod.getUsername().equals(user.getUsername())) {
+            String oldUsername = user.getUsername();
+
+            user.setUsername(userMod.getUsername());
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+
+            for (ExternalResource resource : user.getResources()) {
+                for (MappingItem mapItem : resource.getUmapping().getItems()) {
+                    if (mapItem.isAccountid() && mapItem.getIntMappingType() == IntMappingType.Username) {
+                        propByRes.addOldAccountId(resource.getKey(), oldUsername);
+                    }
+                }
+            }
+        }
+
+        // security question / answer:
+        // userMod.getSecurityQuestion() is null => remove user security question and answer
+        // userMod.getSecurityQuestion() == 0 => don't change anything
+        // userMod.getSecurityQuestion() > 0 => update user security question and answer
+        if (userMod.getSecurityQuestion() == null) {
+            user.setSecurityQuestion(null);
+            user.setSecurityAnswer(null);
+        } else if (userMod.getSecurityQuestion() > 0) {
+            SecurityQuestion securityQuestion = securityQuestionDAO.find(userMod.getSecurityQuestion());
+            if (securityQuestion != null) {
+                user.setSecurityQuestion(securityQuestion);
+                user.setSecurityAnswer(userMod.getSecurityAnswer());
+            }
+        }
+
+        // attributes, derived attributes, virtual attributes and resources
+        propByRes.merge(fill(user, userMod, attrUtilFactory.getInstance(AttributableType.USER), scce));
+
+        // store the role ids of membership required to be added
+        Set<Long> membershipToBeAddedRoleIds = new HashSet<>();
+        for (MembershipMod membToBeAdded : userMod.getMembershipsToAdd()) {
+            membershipToBeAddedRoleIds.add(membToBeAdded.getRole());
+        }
+
+        final Set<String> toBeDeprovisioned = new HashSet<>();
+        final Set<String> toBeProvisioned = new HashSet<>();
+
+        // memberships to be removed
+        for (Long membershipId : userMod.getMembershipsToRemove()) {
+            LOG.debug("Membership to be removed: {}", membershipId);
+
+            Membership membership = membershipDAO.find(membershipId);
+            if (membership == null) {
+                LOG.debug("Invalid membership id specified to be removed: {}", membershipId);
+            } else {
+                if (!membershipToBeAddedRoleIds.contains(membership.getRole().getKey())) {
+                    toBeDeprovisioned.addAll(membership.getRole().getResourceNames());
+                }
+
+                // In order to make the removeMembership() below to work,
+                // we need to be sure to take exactly the same membership
+                // of the user object currently in memory (which has potentially
+                // some modifications compared to the one stored in the DB
+                membership = user.getMembership(membership.getRole().getKey());
+                if (membershipToBeAddedRoleIds.contains(membership.getRole().getKey())) {
+                    Set<Long> attributeIds = new HashSet<>(membership.getPlainAttrs().size());
+                    for (PlainAttr attribute : membership.getPlainAttrs()) {
+                        attributeIds.add(attribute.getKey());
+                    }
+                    for (Long attributeId : attributeIds) {
+                        plainAttrDAO.delete(attributeId, MPlainAttr.class);
+                    }
+                    attributeIds.clear();
+
+                    // remove derived attributes
+                    for (DerAttr derAttr : membership.getDerAttrs()) {
+                        attributeIds.add(derAttr.getKey());
+                    }
+                    for (Long derAttrId : attributeIds) {
+                        derAttrDAO.delete(derAttrId, MDerAttr.class);
+                    }
+                    attributeIds.clear();
+
+                    // remove virtual attributes
+                    for (VirAttr virAttr : membership.getVirAttrs()) {
+                        attributeIds.add(virAttr.getKey());
+                    }
+                    for (Long virAttrId : attributeIds) {
+                        virAttrDAO.delete(virAttrId, MVirAttr.class);
+                    }
+                    attributeIds.clear();
+                } else {
+                    user.removeMembership(membership);
+
+                    membershipDAO.delete(membershipId);
+                }
+            }
+        }
+
+        // memberships to be added
+        for (MembershipMod membershipMod : userMod.getMembershipsToAdd()) {
+            LOG.debug("Membership to be added: role({})", membershipMod.getRole());
+
+            Role role = roleDAO.find(membershipMod.getRole());
+            if (role == null) {
+                LOG.debug("Ignoring invalid role {}", membershipMod.getRole());
+            } else {
+                Membership membership = user.getMembership(role.getKey());
+                if (membership == null) {
+                    membership = entityFactory.newEntity(Membership.class);
+                    membership.setRole(role);
+                    membership.setUser(user);
+
+                    user.addMembership(membership);
+
+                    toBeProvisioned.addAll(role.getResourceNames());
+                }
+
+                propByRes.merge(fill(membership, membershipMod,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP), scce));
+            }
+        }
+
+        propByRes.addAll(ResourceOperation.DELETE, toBeDeprovisioned);
+        propByRes.addAll(ResourceOperation.UPDATE, toBeProvisioned);
+
+        /**
+         * In case of new memberships all the current resources have to be updated in order to propagate new role and
+         * membership attribute values.
+         */
+        if (!toBeDeprovisioned.isEmpty() || !toBeProvisioned.isEmpty()) {
+            currentResources.removeAll(toBeDeprovisioned);
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+        }
+
+        return propByRes;
+    }
+
+    @Transactional(readOnly = true)
+    public UserTO getUserTO(final User user) {
+        UserTO userTO = new UserTO();
+
+        BeanUtils.copyProperties(user, userTO, IGNORE_USER_PROPERTIES);
+
+        if (user.getSecurityQuestion() != null) {
+            userTO.setSecurityQuestion(user.getSecurityQuestion().getKey());
+        }
+
+        connObjectUtil.retrieveVirAttrValues(user, attrUtilFactory.getInstance(AttributableType.USER));
+        fillTO(userTO, user.getPlainAttrs(), user.getDerAttrs(), user.getVirAttrs(), user.getResources());
+
+        MembershipTO membershipTO;
+        for (Membership membership : user.getMemberships()) {
+            membershipTO = new MembershipTO();
+
+            // set sys info
+            membershipTO.setCreator(membership.getCreator());
+            membershipTO.setCreationDate(membership.getCreationDate());
+            membershipTO.setLastModifier(membership.getLastModifier());
+            membershipTO.setLastChangeDate(membership.getLastChangeDate());
+
+            membershipTO.setKey(membership.getKey());
+            membershipTO.setRoleId(membership.getRole().getKey());
+            membershipTO.setRoleName(membership.getRole().getName());
+
+            // SYNCOPE-458 retrieve also membership virtual attributes
+            connObjectUtil.retrieveVirAttrValues(membership, attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
+
+            fillTO(membershipTO,
+                    membership.getPlainAttrs(), membership.getDerAttrs(), membership.getVirAttrs(),
+                    Collections.<ExternalResource>emptyList());
+
+            userTO.getMemberships().add(membershipTO);
+        }
+
+        return userTO;
+    }
+
+    @Transactional(readOnly = true)
+    public UserTO getUserTO(final String username) {
+        return getUserTO(userDAO.authFecthUser(username));
+    }
+
+    @Transactional(readOnly = true)
+    public UserTO getUserTO(final Long key) {
+        return getUserTO(userDAO.authFecthUser(key));
+    }
+
+    /**
+     * SYNCOPE-459: build virtual attribute changes in case no other changes were made.
+     *
+     * @param key user id
+     * @param vAttrsToBeRemoved virtual attributes to be removed.
+     * @param vAttrsToBeUpdated virtual attributes to be updated.
+     * @return operations to be performed on external resources for virtual attributes changes
+     */
+    public PropagationByResource fillVirtual(
+            final Long key, final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated) {
+
+        return fillVirtual(
+                userDAO.authFecthUser(key),
+                vAttrsToBeRemoved,
+                vAttrsToBeUpdated,
+                attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    /**
+     * SYNCOPE-501: build membership virtual attribute changes in case no other changes were made.
+     *
+     * @param key user id
+     * @param roleId role id
+     * @param membershipId membership id
+     * @param vAttrsToBeRemoved virtual attributes to be removed.
+     * @param vAttrsToBeUpdated virtual attributes to be updated.
+     * @param isRemoval flag to check if fill is on removed or added membership
+     * @return operations to be performed on external resources for membership virtual attributes changes
+     */
+    public PropagationByResource fillMembershipVirtual(
+            final Long key, final Long roleId, final Long membershipId, final Set<String> vAttrsToBeRemoved,
+            final Set<AttrMod> vAttrsToBeUpdated, final boolean isRemoval) {
+
+        final Membership membership = membershipId == null
+                ? userDAO.authFecthUser(key).getMembership(roleId)
+                : getMembershipFromId(membershipId);
+
+        return membership == null ? new PropagationByResource() : isRemoval
+                ? fillVirtual(
+                        membership,
+                        membership.getVirAttrs() == null
+                                ? Collections.<String>emptySet()
+                                : getAttributeNames(membership.getVirAttrs()),
+                        vAttrsToBeUpdated,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP))
+                : fillVirtual(
+                        membership,
+                        vAttrsToBeRemoved,
+                        vAttrsToBeUpdated,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
+    }
+
+    private Set<String> getAttributeNames(final List<? extends VirAttr> virAttrs) {
+        final Set<String> virAttrNames = new HashSet<String>();
+        for (VirAttr attr : virAttrs) {
+            virAttrNames.add(attr.getSchema().getKey());
+        }
+        return virAttrNames;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/ImplementationClassNamesLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/ImplementationClassNamesLoader.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/ImplementationClassNamesLoader.java
new file mode 100644
index 0000000..4219881
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/ImplementationClassNamesLoader.java
@@ -0,0 +1,141 @@
+/*
+ * 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.server.logic.init;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.wrap.Validator;
+import org.apache.syncope.provisioning.api.job.PushJob;
+import org.apache.syncope.provisioning.api.job.SyncJob;
+import org.apache.syncope.provisioning.api.job.TaskJob;
+import org.apache.syncope.provisioning.api.propagation.PropagationActions;
+import org.apache.syncope.provisioning.api.sync.PushActions;
+import org.apache.syncope.provisioning.api.sync.SyncActions;
+import org.apache.syncope.provisioning.api.sync.SyncCorrelationRule;
+import org.apache.syncope.server.logic.report.Reportlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Cache class names for all implementations of Syncope interfaces found in classpath, for later usage.
+ */
+@Component
+public class ImplementationClassNamesLoader {
+
+    public enum Type {
+
+        REPORTLET,
+        TASKJOB,
+        SYNC_ACTIONS,
+        PUSH_ACTIONS,
+        SYNC_CORRELATION_RULES,
+        PUSH_CORRELATION_RULES,
+        PROPAGATION_ACTIONS,
+        VALIDATOR
+
+    }
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ImplementationClassNamesLoader.class);
+
+    @Autowired
+    private ResourcePatternResolver resResolver;
+
+    private Map<Type, Set<String>> classNames;
+
+    public void load() {
+        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
+
+        classNames = new EnumMap<Type, Set<String>>(Type.class);
+        for (Type type : Type.values()) {
+            classNames.put(type, new HashSet<String>());
+        }
+
+        try {
+            for (Resource resource : resResolver.getResources("classpath*:**/*.class")) {
+                ClassMetadata metadata = factory.getMetadataReader(resource).getClassMetadata();
+
+                try {
+                    Class<?> clazz = ClassUtils.forName(metadata.getClassName(), ClassUtils.getDefaultClassLoader());
+                    Set<Class<?>> interfaces = ClassUtils.getAllInterfacesForClassAsSet(clazz);
+
+                    if (interfaces.contains(Reportlet.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.REPORTLET).add(clazz.getName());
+                    }
+
+                    if ((interfaces.contains(TaskJob.class))
+                            && !metadata.isAbstract()
+                            && !SyncJob.class.getName().equals(metadata.getClassName())
+                            && !PushJob.class.getName().equals(metadata.getClassName())) {
+
+                        classNames.get(Type.TASKJOB).add(metadata.getClassName());
+                    }
+
+                    if (interfaces.contains(SyncActions.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.SYNC_ACTIONS).add(metadata.getClassName());
+                    }
+
+                    if (interfaces.contains(PushActions.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.PUSH_ACTIONS).add(metadata.getClassName());
+                    }
+
+                    if (interfaces.contains(SyncCorrelationRule.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.SYNC_CORRELATION_RULES).add(metadata.getClassName());
+                    }
+
+                    // TODO: add push correlation rules management
+                    if (interfaces.contains(PropagationActions.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.PROPAGATION_ACTIONS).add(metadata.getClassName());
+                    }
+
+                    if (interfaces.contains(Validator.class) && !metadata.isAbstract()) {
+                        classNames.get(Type.VALIDATOR).add(metadata.getClassName());
+                    }
+                } catch (ClassNotFoundException e) {
+                    LOG.warn("Could not load class {}", metadata.getClassName());
+                } catch (LinkageError e) {
+                    LOG.warn("Could not link class {}", metadata.getClassName());
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("While searching for implementatiom classes", e);
+        }
+
+        classNames = Collections.unmodifiableMap(classNames);
+
+        LOG.debug("Implementation classes found: {}", classNames);
+    }
+
+    public Set<String> getClassNames(final Type type) {
+        return classNames.get(type);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/JobInstanceLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/JobInstanceLoader.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/JobInstanceLoader.java
new file mode 100644
index 0000000..8944777
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/JobInstanceLoader.java
@@ -0,0 +1,287 @@
+/*
+ * 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.server.logic.init;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.ReportDAO;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.conf.CPlainAttr;
+import org.apache.syncope.persistence.api.entity.task.PushTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.provisioning.api.job.SyncJob;
+import org.apache.syncope.provisioning.api.job.TaskJob;
+import org.apache.syncope.provisioning.api.sync.SyncActions;
+import org.apache.syncope.server.logic.notification.NotificationJob;
+import org.apache.syncope.server.logic.report.ReportJob;
+import org.apache.syncope.server.spring.ApplicationContextProvider;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerKey;
+import org.quartz.impl.JobDetailImpl;
+import org.quartz.impl.triggers.CronTriggerImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class JobInstanceLoader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JobInstanceLoader.class);
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Autowired
+    private ReportDAO reportDAO;
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    private static Long getIdFromJobName(final String name, final String pattern, final int prefixLength) {
+        Long result = null;
+
+        Matcher jobMatcher = Pattern.compile(pattern).matcher(name);
+        if (jobMatcher.matches()) {
+            try {
+                result = Long.valueOf(name.substring(prefixLength));
+            } catch (NumberFormatException e) {
+                LOG.error("Unparsable id: {}", name.substring(prefixLength), e);
+            }
+        }
+
+        return result;
+    }
+
+    public static Long getTaskIdFromJobName(final String name) {
+        return getIdFromJobName("taskJob[0-9]+", name, 7);
+    }
+
+    public static Long getReportIdFromJobName(final String name) {
+        return getIdFromJobName("reportJob[0-9]+", name, 9);
+    }
+
+    public static String getJobName(final Task task) {
+        return task == null
+                ? "taskNotificationJob"
+                : "taskJob" + task.getKey();
+    }
+
+    public static String getJobName(final Report report) {
+        return "reportJob" + report.getKey();
+    }
+
+    public static String getTriggerName(final String jobName) {
+        return "Trigger_" + jobName;
+    }
+
+    private void registerJob(final String jobName, final Job jobInstance, final String cronExpression)
+            throws SchedulerException, ParseException {
+
+        synchronized (scheduler.getScheduler()) {
+            boolean jobAlreadyRunning = false;
+            for (JobExecutionContext jobCtx : scheduler.getScheduler().getCurrentlyExecutingJobs()) {
+                if (jobName.equals(jobCtx.getJobDetail().getKey().getName())
+                        && Scheduler.DEFAULT_GROUP.equals(jobCtx.getJobDetail().getKey().getGroup())) {
+
+                    jobAlreadyRunning = true;
+
+                    LOG.debug("Job {} already running, cancel", jobCtx.getJobDetail().getKey());
+                }
+            }
+
+            if (jobAlreadyRunning) {
+                return;
+            }
+        }
+
+        // 0. unregister job
+        unregisterJob(jobName);
+
+        // 1. Job bean
+        ApplicationContextProvider.getBeanFactory().registerSingleton(jobName, jobInstance);
+
+        // 2. JobDetail bean
+        JobDetailImpl jobDetail = new JobDetailImpl();
+        jobDetail.setName(jobName);
+        jobDetail.setGroup(Scheduler.DEFAULT_GROUP);
+        jobDetail.setJobClass(jobInstance.getClass());
+
+        // 3. Trigger
+        if (cronExpression == null) {
+            // Jobs added with no trigger must be durable
+            jobDetail.setDurability(true);
+            scheduler.getScheduler().addJob(jobDetail, true);
+        } else {
+            CronTriggerImpl cronTrigger = new CronTriggerImpl();
+            cronTrigger.setName(getTriggerName(jobName));
+            cronTrigger.setCronExpression(cronExpression);
+
+            scheduler.getScheduler().scheduleJob(jobDetail, cronTrigger);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void registerJob(final Task task, final String jobClassName, final String cronExpression)
+            throws ClassNotFoundException, SchedulerException, ParseException {
+
+        final Class<?> jobClass = Class.forName(jobClassName);
+        Job jobInstance = (Job) ApplicationContextProvider.getBeanFactory().
+                createBean(jobClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+        if (jobInstance instanceof TaskJob) {
+            ((TaskJob) jobInstance).setTaskId(task.getKey());
+        }
+
+        // In case of synchronization job/task retrieve and set synchronization actions:
+        // actions cannot be changed at runtime but connector and synchronization policies (reloaded at execution time).
+        if (jobInstance instanceof SyncJob && task instanceof SyncTask) {
+            final List<SyncActions> actions = new ArrayList<>();
+            for (String className : ((SyncTask) task).getActionsClassNames()) {
+                try {
+                    Class<?> actionsClass = Class.forName(className);
+
+                    final SyncActions syncActions =
+                            (SyncActions) ApplicationContextProvider.getBeanFactory().
+                            createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+
+                    actions.add(syncActions);
+                } catch (Exception e) {
+                    LOG.info("Class '{}' not found", className, e);
+                }
+            }
+
+            ((SyncJob) jobInstance).setActions(actions);
+        }
+
+        registerJob(getJobName(task), jobInstance, cronExpression);
+    }
+
+    @Transactional(readOnly = true)
+    public void registerTaskJob(final Long taskId)
+            throws ClassNotFoundException, SchedulerException, ParseException {
+
+        SchedTask task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new NotFoundException("Task " + taskId);
+        } else {
+            registerJob(task, task.getJobClassName(), task.getCronExpression());
+        }
+    }
+
+    public void registerJob(final Report report) throws SchedulerException, ParseException {
+        Job jobInstance = (Job) ApplicationContextProvider.getBeanFactory().
+                createBean(ReportJob.class, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+        ((ReportJob) jobInstance).setReportKey(report.getKey());
+
+        registerJob(getJobName(report), jobInstance, report.getCronExpression());
+    }
+
+    @Transactional(readOnly = true)
+    public void registerReportJob(final Long reportId) throws SchedulerException, ParseException {
+        Report report = reportDAO.find(reportId);
+        if (report == null) {
+            throw new NotFoundException("Report " + reportId);
+        } else {
+            registerJob(report);
+        }
+    }
+
+    private void unregisterJob(final String jobName) {
+        try {
+            scheduler.getScheduler().unscheduleJob(new TriggerKey(jobName, Scheduler.DEFAULT_GROUP));
+            scheduler.getScheduler().deleteJob(new JobKey(jobName, Scheduler.DEFAULT_GROUP));
+        } catch (SchedulerException e) {
+            LOG.error("Could not remove job " + jobName, e);
+        }
+
+        if (ApplicationContextProvider.getBeanFactory().containsSingleton(jobName)) {
+            ApplicationContextProvider.getBeanFactory().destroySingleton(jobName);
+        }
+    }
+
+    public void unregisterJob(final Task task) {
+        unregisterJob(getJobName(task));
+    }
+
+    public void unregisterJob(final Report report) {
+        unregisterJob(getJobName(report));
+    }
+
+    @Transactional
+    public void load() {
+        // 1. jobs for SchedTasks
+        Set<SchedTask> tasks = new HashSet<>(taskDAO.<SchedTask>findAll(TaskType.SCHEDULED));
+        tasks.addAll(taskDAO.<SyncTask>findAll(TaskType.SYNCHRONIZATION));
+        tasks.addAll(taskDAO.<PushTask>findAll(TaskType.PUSH));
+        for (SchedTask task : tasks) {
+            try {
+                registerJob(task, task.getJobClassName(), task.getCronExpression());
+            } catch (Exception e) {
+                LOG.error("While loading job instance for task " + task.getKey(), e);
+            }
+        }
+
+        // 2. NotificationJob
+        CPlainAttr notificationJobCronExp =
+                confDAO.find("notificationjob.cronExpression", NotificationJob.DEFAULT_CRON_EXP);
+        if (StringUtils.isBlank(notificationJobCronExp.getValuesAsStrings().get(0))) {
+            LOG.debug("Empty value provided for NotificationJob's cron, not registering anything on Quartz");
+        } else {
+            LOG.debug("NotificationJob's cron expression: {} - registering Quartz job and trigger",
+                    notificationJobCronExp);
+
+            try {
+                registerJob(null, NotificationJob.class.getName(), notificationJobCronExp.getValuesAsStrings().get(0));
+            } catch (Exception e) {
+                LOG.error("While loading NotificationJob instance", e);
+            }
+        }
+
+        // 3. ReportJobs
+        for (Report report : reportDAO.findAll()) {
+            try {
+                registerJob(report);
+            } catch (Exception e) {
+                LOG.error("While loading job instance for report " + report.getName(), e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/WorkflowAdapterLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/WorkflowAdapterLoader.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/WorkflowAdapterLoader.java
new file mode 100644
index 0000000..5844277
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/init/WorkflowAdapterLoader.java
@@ -0,0 +1,88 @@
+/*
+ * 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.server.logic.init;
+
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowInstanceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class WorkflowAdapterLoader implements BeanFactoryAware {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WorkflowAdapterLoader.class);
+
+    @Autowired
+    private UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    private RoleWorkflowAdapter rwfAdapter;
+
+    private DefaultListableBeanFactory beanFactory;
+
+    private WorkflowInstanceLoader wfLoader;
+
+    @Override
+    public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
+        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
+    }
+
+    private void lazyInit() {
+        if (wfLoader == null) {
+            if (uwfAdapter.getLoaderClass() != null) {
+                wfLoader = (WorkflowInstanceLoader) beanFactory.createBean(
+                        uwfAdapter.getLoaderClass(), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+            }
+            if (rwfAdapter.getLoaderClass() != null) {
+                wfLoader = (WorkflowInstanceLoader) beanFactory.createBean(
+                        rwfAdapter.getLoaderClass(), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+            }
+        }
+    }
+
+    public String getTablePrefix() {
+        lazyInit();
+        return wfLoader == null ? null : wfLoader.getTablePrefix();
+    }
+
+    public void init() {
+        lazyInit();
+        if (wfLoader != null) {
+            wfLoader.init();
+        }
+    }
+
+    public void load() {
+        lazyInit();
+        if (wfLoader == null) {
+            LOG.debug("Configured workflow adapter does not need loading");
+        } else {
+            LOG.debug("Loading workflow adapter by {}", wfLoader.getClass().getName());
+            wfLoader.load();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationJob.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationJob.java
new file mode 100644
index 0000000..77189e1
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationJob.java
@@ -0,0 +1,280 @@
+/*
+ * 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.server.logic.notification;
+
+import java.util.Date;
+import java.util.Properties;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.logic.audit.AuditManager;
+import org.apache.syncope.server.utils.ExceptionUtil;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+
+/**
+ * Periodically checks for notification to send.
+ *
+ * @see NotificationTask
+ */
+@DisallowConcurrentExecution
+public class NotificationJob implements Job {
+
+    enum Status {
+
+        SENT,
+        NOT_SENT
+
+    }
+
+    public static final String DEFAULT_CRON_EXP = "0 0/5 * * * ?";
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationJob.class);
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    private TaskDAO taskDAO;
+
+    private long maxRetries;
+
+    private void init() {
+        maxRetries = notificationManager.getMaxRetries();
+
+        if (mailSender instanceof JavaMailSenderImpl
+                && StringUtils.isNotBlank(((JavaMailSenderImpl) mailSender).getUsername())) {
+
+            Properties javaMailProperties = ((JavaMailSenderImpl) mailSender).getJavaMailProperties();
+            javaMailProperties.setProperty("mail.smtp.auth", "true");
+            ((JavaMailSenderImpl) mailSender).setJavaMailProperties(javaMailProperties);
+        }
+    }
+
+    public TaskExec executeSingle(final NotificationTask task) {
+        init();
+
+        TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setTask(task);
+        execution.setStartDate(new Date());
+
+        boolean retryPossible = true;
+
+        if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
+                || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
+
+            String message = "Could not fetch all required information for sending e-mails:\n"
+                    + task.getRecipients() + "\n"
+                    + task.getSender() + "\n"
+                    + task.getSubject() + "\n"
+                    + task.getHtmlBody() + "\n"
+                    + task.getTextBody();
+            LOG.error(message);
+
+            execution.setStatus(Status.NOT_SENT.name());
+            retryPossible = false;
+
+            if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                execution.setMessage(message);
+            }
+        } else {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("About to send e-mails:\n"
+                        + task.getRecipients() + "\n"
+                        + task.getSender() + "\n"
+                        + task.getSubject() + "\n"
+                        + task.getHtmlBody() + "\n"
+                        + task.getTextBody() + "\n");
+            }
+
+            for (String to : task.getRecipients()) {
+                try {
+                    MimeMessage message = mailSender.createMimeMessage();
+                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
+                    helper.setTo(to);
+                    helper.setFrom(task.getSender());
+                    helper.setSubject(task.getSubject());
+                    helper.setText(task.getTextBody(), task.getHtmlBody());
+
+                    mailSender.send(message);
+
+                    execution.setStatus(Status.SENT.name());
+
+                    StringBuilder report = new StringBuilder();
+                    switch (task.getTraceLevel()) {
+                        case ALL:
+                            report.append("FROM: ").append(task.getSender()).append('\n').
+                                    append("TO: ").append(to).append('\n').
+                                    append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
+                                    append(task.getTextBody()).append('\n').append('\n').
+                                    append(task.getHtmlBody()).append('\n');
+                            break;
+
+                        case SUMMARY:
+                            report.append("E-mail sent to ").append(to).append('\n');
+                            break;
+
+                        case FAILURES:
+                        case NONE:
+                        default:
+                    }
+                    if (report.length() > 0) {
+                        execution.setMessage(report.toString());
+                    }
+
+                    auditManager.audit(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            Result.SUCCESS,
+                            null,
+                            null,
+                            task,
+                            "Successfully sent notification to " + to);
+                } catch (Exception e) {
+                    LOG.error("Could not send e-mail", e);
+
+                    execution.setStatus(Status.NOT_SENT.name());
+                    if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+                        execution.setMessage(ExceptionUtil.getFullStackTrace(e));
+                    }
+
+                    auditManager.audit(
+                            AuditElements.EventCategoryType.TASK,
+                            "notification",
+                            null,
+                            "send",
+                            Result.FAILURE,
+                            null,
+                            null,
+                            task,
+                            "Could not send notification to " + to, e);
+                }
+
+                execution.setEndDate(new Date());
+            }
+        }
+
+        if (hasToBeRegistered(execution)) {
+            execution = notificationManager.storeExec(execution);
+            if (retryPossible && (Status.valueOf(execution.getStatus()) == Status.NOT_SENT)) {
+                handleRetries(execution);
+            }
+        } else {
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
+        }
+
+        return execution;
+    }
+
+    @Override
+    public void execute(final JobExecutionContext context)
+            throws JobExecutionException {
+
+        LOG.debug("Waking up...");
+
+        for (NotificationTask task : taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION)) {
+            LOG.debug("Found notification task {} to be executed: starting...", task);
+            executeSingle(task);
+            LOG.debug("Notification task {} executed", task);
+        }
+
+        LOG.debug("Sleeping again...");
+    }
+
+    private boolean hasToBeRegistered(final TaskExec execution) {
+        NotificationTask task = (NotificationTask) execution.getTask();
+
+        // True if either failed and failures have to be registered, or if ALL
+        // has to be registered.
+        return (Status.valueOf(execution.getStatus()) == Status.NOT_SENT
+                && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                || task.getTraceLevel() == TraceLevel.ALL;
+    }
+
+    private void handleRetries(final TaskExec execution) {
+        if (maxRetries <= 0) {
+            return;
+        }
+
+        long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
+                execution.getTask().getKey(), Status.NOT_SENT.name());
+
+        if (failedExecutionsCount <= maxRetries) {
+            LOG.debug("Execution of notification task {} will be retried [{}/{}]",
+                    execution.getTask(), failedExecutionsCount, maxRetries);
+            notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    Result.SUCCESS,
+                    null,
+                    null,
+                    execution,
+                    "Notification task " + execution.getTask().getKey() + " will be retried");
+        } else {
+            LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.TASK,
+                    "notification",
+                    null,
+                    "retry",
+                    Result.FAILURE,
+                    null,
+                    null,
+                    execution,
+                    "Giving up retries on notification task " + execution.getTask().getKey());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationManager.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationManager.java
new file mode 100644
index 0000000..68c3a78
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/notification/NotificationManager.java
@@ -0,0 +1,441 @@
+/*
+ * 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.server.logic.notification;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.Notification;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.server.logic.data.RoleDataBinder;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.logic.search.SearchCondConverter;
+import org.apache.syncope.server.utils.ConnObjectUtil;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.ToolManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Create notification tasks that will be executed by NotificationJob.
+ *
+ * @see NotificationTask
+ */
+@Transactional(rollbackFor = { Throwable.class })
+public class NotificationManager {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationManager.class);
+
+    public static final String MAIL_TEMPLATES = "mailTemplates/";
+
+    public static final String MAIL_TEMPLATE_HTML_SUFFIX = ".html.vm";
+
+    public static final String MAIL_TEMPLATE_TEXT_SUFFIX = ".txt.vm";
+
+    /**
+     * Notification DAO.
+     */
+    @Autowired
+    private NotificationDAO notificationDAO;
+
+    /**
+     * Configuration DAO.
+     */
+    @Autowired
+    private ConfDAO confDAO;
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    private UserDAO userDAO;
+
+    /**
+     * Role DAO.
+     */
+    @Autowired
+    private RoleDAO roleDAO;
+
+    /**
+     * User data binder.
+     */
+    @Autowired
+    private UserDataBinder userDataBinder;
+
+    /**
+     * Role data binder.
+     */
+    @Autowired
+    private RoleDataBinder roleDataBinder;
+
+    /**
+     * User Search DAO.
+     */
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    private TaskDAO taskDAO;
+
+    /**
+     * Velocity template engine.
+     */
+    @Autowired
+    private VelocityEngine velocityEngine;
+
+    /**
+     * Velocity tool manager.
+     */
+    @Autowired
+    private ToolManager velocityToolManager;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    @Transactional(readOnly = true)
+    public long getMaxRetries() {
+        return confDAO.find("notification.maxRetries", "0").getValues().get(0).getLongValue();
+    }
+
+    /**
+     * Create a notification task.
+     *
+     * @param notification notification to take as model
+     * @param attributable the user this task is about
+     * @param model Velocity model
+     * @return notification task, fully populated
+     */
+    private NotificationTask getNotificationTask(
+            final Notification notification,
+            final Attributable<?, ?, ?> attributable,
+            final Map<String, Object> model) {
+
+        if (attributable != null) {
+            connObjectUtil.retrieveVirAttrValues(attributable,
+                    attrUtilFactory.getInstance(
+                            attributable instanceof User ? AttributableType.USER : AttributableType.ROLE));
+        }
+
+        final List<User> recipients = new ArrayList<>();
+
+        if (notification.getRecipients() != null) {
+            recipients.addAll(searchDAO.<User>search(RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()),
+                    SearchCondConverter.convert(notification.getRecipients()),
+                    Collections.<OrderByClause>emptyList(), SubjectType.USER));
+        }
+
+        if (notification.isSelfAsRecipient() && attributable instanceof User) {
+            recipients.add((User) attributable);
+        }
+
+        final Set<String> recipientEmails = new HashSet<>();
+        final List<UserTO> recipientTOs = new ArrayList<>(recipients.size());
+        for (User recipient : recipients) {
+            connObjectUtil.retrieveVirAttrValues(recipient, attrUtilFactory.getInstance(AttributableType.USER));
+
+            String email = getRecipientEmail(notification.getRecipientAttrType(),
+                    notification.getRecipientAttrName(), recipient);
+            if (email == null) {
+                LOG.warn("{} cannot be notified: {} not found", recipient, notification.getRecipientAttrName());
+            } else {
+                recipientEmails.add(email);
+                recipientTOs.add(userDataBinder.getUserTO(recipient));
+            }
+        }
+
+        if (notification.getStaticRecipients() != null) {
+            recipientEmails.addAll(notification.getStaticRecipients());
+        }
+
+        model.put("recipients", recipientTOs);
+        model.put("syncopeConf", this.findAllSyncopeConfs());
+        model.put("events", notification.getEvents());
+
+        NotificationTask task = entityFactory.newEntity(NotificationTask.class);
+        task.setTraceLevel(notification.getTraceLevel());
+        task.getRecipients().addAll(recipientEmails);
+        task.setSender(notification.getSender());
+        task.setSubject(notification.getSubject());
+
+        String htmlBody = mergeTemplateIntoString(
+                MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_HTML_SUFFIX, model);
+        String textBody = mergeTemplateIntoString(
+                MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_TEXT_SUFFIX, model);
+
+        task.setHtmlBody(htmlBody);
+        task.setTextBody(textBody);
+
+        return task;
+    }
+
+    private String mergeTemplateIntoString(final String templateLocation, final Map<String, Object> model) {
+        StringWriter result = new StringWriter();
+        try {
+            Context velocityContext = createVelocityContext(model);
+            velocityEngine.mergeTemplate(templateLocation, SyncopeConstants.DEFAULT_ENCODING, velocityContext, result);
+        } catch (VelocityException e) {
+            LOG.error("Could not get mail body", e);
+        } catch (RuntimeException e) {
+            // ensure same behaviour as by using Spring VelocityEngineUtils.mergeTemplateIntoString()
+            throw e;
+        } catch (Exception e) {
+            LOG.error("Could not get mail body", e);
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Create a Velocity Context for the given model, to be passed to the template for merging.
+     *
+     * @param model Velocity model
+     * @return Velocity context
+     */
+    protected Context createVelocityContext(Map<String, Object> model) {
+        Context toolContext = velocityToolManager.createContext();
+        return new VelocityContext(model, toolContext);
+    }
+
+    /**
+     * Create notification tasks for each notification matching the given user id and (some of) tasks performed.
+     */
+    public void createTasks(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final Result condition,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        SubjectType subjectType = null;
+        Subject<?, ?, ?> subject = null;
+
+        if (before instanceof UserTO) {
+            subjectType = SubjectType.USER;
+            subject = userDAO.find(((UserTO) before).getKey());
+        } else if (output instanceof UserTO) {
+            subjectType = SubjectType.USER;
+            subject = userDAO.find(((UserTO) output).getKey());
+        } else if (before instanceof RoleTO) {
+            subjectType = SubjectType.ROLE;
+            subject = roleDAO.find(((RoleTO) before).getKey());
+        } else if (output instanceof RoleTO) {
+            subjectType = SubjectType.ROLE;
+            subject = roleDAO.find(((RoleTO) output).getKey());
+        }
+
+        LOG.debug("Search notification for [{}]{}", subjectType, subject);
+
+        for (Notification notification : notificationDAO.findAll()) {
+            LOG.debug("Notification available user about {}", notification.getUserAbout());
+            LOG.debug("Notification available role about {}", notification.getRoleAbout());
+            if (notification.isActive()) {
+
+                final Set<String> events = new HashSet<>(notification.getEvents());
+                events.retainAll(Collections.<String>singleton(AuditLoggerName.buildEvent(
+                        type, category, subcategory, event, condition)));
+
+                if (events.isEmpty()) {
+                    LOG.debug("No events found about {}", subject);
+                } else if (subjectType == null || subject == null
+                        || notification.getUserAbout() == null || notification.getRoleAbout() == null
+                        || searchDAO.matches(subject,
+                                SearchCondConverter.convert(notification.getUserAbout()), subjectType)
+                        || searchDAO.matches(subject,
+                                SearchCondConverter.convert(notification.getRoleAbout()), subjectType)) {
+
+                    LOG.debug("Creating notification task for events {} about {}", events, subject);
+
+                    final Map<String, Object> model = new HashMap<>();
+                    model.put("type", type);
+                    model.put("category", category);
+                    model.put("subcategory", subcategory);
+                    model.put("event", event);
+                    model.put("condition", condition);
+                    model.put("before", before);
+                    model.put("output", output);
+                    model.put("input", input);
+
+                    if (subject instanceof User) {
+                        model.put("user", userDataBinder.getUserTO((User) subject));
+                    } else if (subject instanceof Role) {
+                        model.put("role", roleDataBinder.getRoleTO((Role) subject));
+                    }
+
+                    taskDAO.save(getNotificationTask(notification, subject, model));
+                }
+            } else {
+                LOG.debug("Notification {}, userAbout {}, roleAbout {} is deactivated, "
+                        + "notification task will not be created", notification.getKey(),
+                        notification.getUserAbout(), notification.getRoleAbout());
+            }
+        }
+    }
+
+    private String getRecipientEmail(
+            final IntMappingType recipientAttrType, final String recipientAttrName, final User user) {
+
+        String email = null;
+
+        switch (recipientAttrType) {
+            case Username:
+                email = user.getUsername();
+                break;
+
+            case UserSchema:
+                UPlainAttr attr = user.getPlainAttr(recipientAttrName);
+                if (attr != null && !attr.getValuesAsStrings().isEmpty()) {
+                    email = attr.getValuesAsStrings().get(0);
+                }
+                break;
+
+            case UserVirtualSchema:
+                UVirAttr virAttr = user.getVirAttr(recipientAttrName);
+                if (virAttr != null && !virAttr.getValues().isEmpty()) {
+                    email = virAttr.getValues().get(0);
+                }
+                break;
+
+            case UserDerivedSchema:
+                UDerAttr derAttr = user.getDerAttr(recipientAttrName);
+                if (derAttr != null) {
+                    email = derAttr.getValue(user.getPlainAttrs());
+                }
+                break;
+
+            default:
+        }
+
+        return email;
+    }
+
+    /**
+     * Store execution of a NotificationTask.
+     *
+     * @param execution task execution.
+     * @return merged task execution.
+     */
+    public TaskExec storeExec(final TaskExec execution) {
+        NotificationTask task = taskDAO.find(execution.getTask().getKey());
+        task.addExec(execution);
+        task.setExecuted(true);
+        taskDAO.save(task);
+        // this flush call is needed to generate a value for the execution id
+        taskDAO.flush();
+        return execution;
+    }
+
+    /**
+     * Set execution state of NotificationTask with provided id.
+     *
+     * @param taskId task to be updated
+     * @param executed execution state
+     */
+    public void setTaskExecuted(final Long taskId, final boolean executed) {
+        NotificationTask task = taskDAO.find(taskId);
+        task.setExecuted(executed);
+        taskDAO.save(task);
+    }
+
+    /**
+     * Count the number of task executions of a given task with a given status.
+     *
+     * @param taskId task id
+     * @param status status
+     * @return number of task executions
+     */
+    public long countExecutionsWithStatus(final Long taskId, final String status) {
+        NotificationTask task = taskDAO.find(taskId);
+        long count = 0;
+        for (TaskExec taskExec : task.getExecs()) {
+            if (status == null) {
+                if (taskExec.getStatus() == null) {
+                    count++;
+                }
+            } else if (status.equals(taskExec.getStatus())) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    protected Map<String, String> findAllSyncopeConfs() {
+        Map<String, String> syncopeConfMap = new HashMap<>();
+        for (PlainAttr attr : confDAO.get().getPlainAttrs()) {
+            syncopeConfMap.put(attr.getSchema().getKey(), attr.getValuesAsStrings().get(0));
+        }
+        return syncopeConfMap;
+    }
+}


[04/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAConfDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAConfDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAConfDAO.java
index d38b1cd..5e7bc83 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAConfDAO.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAConfDAO.java
@@ -22,10 +22,10 @@ import org.apache.syncope.common.lib.types.AttributableType;
 import org.apache.syncope.persistence.api.dao.ConfDAO;
 import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
 import org.apache.syncope.persistence.api.entity.conf.CPlainAttr;
 import org.apache.syncope.persistence.api.entity.conf.CPlainSchema;
 import org.apache.syncope.persistence.api.entity.conf.Conf;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttr;
 import org.apache.syncope.persistence.jpa.entity.conf.JPAConf;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +41,9 @@ public class JPAConfDAO extends AbstractDAO<Conf, Long> implements ConfDAO {
     @Autowired
     private PlainAttrDAO attrDAO;
 
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
     @Override
     public Conf get() {
         Conf instance = entityManager.find(JPAConf.class, 1L);
@@ -68,7 +71,7 @@ public class JPAConfDAO extends AbstractDAO<Conf, Long> implements ConfDAO {
             result = new JPACPlainAttr();
             result.setSchema(schemaDAO.find(key, CPlainSchema.class));
 
-            result.addValue(defaultValue, JPAAttributableUtil.getInstance(AttributableType.CONFIGURATION));
+            result.addValue(defaultValue, attrUtilFactory.getInstance(AttributableType.CONFIGURATION));
         }
 
         return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
index 5e6209c..8ac1423 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import javax.persistence.NoResultException;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
@@ -31,12 +32,14 @@ import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.persistence.api.RoleEntitlementUtil;
 import org.apache.syncope.persistence.api.dao.DerAttrDAO;
 import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
 import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.persistence.api.dao.RoleDAO;
 import org.apache.syncope.persistence.api.dao.UserDAO;
 import org.apache.syncope.persistence.api.dao.VirAttrDAO;
 import org.apache.syncope.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.persistence.api.entity.AttrTemplate;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
 import org.apache.syncope.persistence.api.entity.DerAttr;
 import org.apache.syncope.persistence.api.entity.Entitlement;
 import org.apache.syncope.persistence.api.entity.ExternalResource;
@@ -60,9 +63,10 @@ import org.apache.syncope.persistence.api.entity.role.RVirAttr;
 import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
 import org.apache.syncope.persistence.api.entity.role.Role;
 import org.apache.syncope.persistence.api.entity.user.User;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.apache.syncope.persistence.jpa.entity.membership.JPAMembership;
 import org.apache.syncope.persistence.jpa.entity.role.JPARole;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
@@ -85,6 +89,9 @@ public class JPARoleDAO extends AbstractSubjectDAO<RPlainAttr, RDerAttr, RVirAtt
     @Autowired
     private EntitlementDAO entitlementDAO;
 
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
     @Override
     protected Subject<RPlainAttr, RDerAttr, RVirAttr> findInternal(final Long key) {
         return find(key);
@@ -321,27 +328,27 @@ public class JPARoleDAO extends AbstractSubjectDAO<RPlainAttr, RDerAttr, RVirAtt
     @Override
     public List<Role> findByAttrValue(final String schemaName, final RPlainAttrValue attrValue) {
         return (List<Role>) findByAttrValue(
-                schemaName, attrValue, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+                schemaName, attrValue, attrUtilFactory.getInstance(AttributableType.ROLE));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public Role findByAttrUniqueValue(final String schemaName, final RPlainAttrValue attrUniqueValue) {
         return (Role) findByAttrUniqueValue(schemaName, attrUniqueValue,
-                JPAAttributableUtil.getInstance(AttributableType.ROLE));
+                attrUtilFactory.getInstance(AttributableType.ROLE));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public List<Role> findByDerAttrValue(final String schemaName, final String value) {
         return (List<Role>) findByDerAttrValue(
-                schemaName, value, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+                schemaName, value, attrUtilFactory.getInstance(AttributableType.ROLE));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public List<Role> findByResource(final ExternalResource resource) {
-        return (List<Role>) findByResource(resource, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+        return (List<Role>) findByResource(resource, attrUtilFactory.getInstance(AttributableType.ROLE));
     }
 
     @Override
@@ -528,4 +535,23 @@ public class JPARoleDAO extends AbstractSubjectDAO<RPlainAttr, RDerAttr, RVirAtt
 
         delete(role);
     }
+
+    @Override
+    public Role authFetchRole(Long key) {
+        if (key == null) {
+            throw new NotFoundException("Null role id");
+        }
+
+        Role role = find(key);
+        if (role == null) {
+            throw new NotFoundException("Role " + key);
+        }
+
+        Set<Long> allowedRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        if (!allowedRoleIds.contains(role.getKey())) {
+            throw new UnauthorizedRoleException(role.getKey());
+        }
+        return role;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
index 86e596f..e71488d 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
@@ -46,10 +46,10 @@ import org.apache.syncope.persistence.api.dao.search.ResourceCond;
 import org.apache.syncope.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.persistence.api.dao.search.SubjectCond;
 import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
 import org.apache.syncope.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.persistence.api.entity.PlainSchema;
 import org.apache.syncope.persistence.api.entity.Subject;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
@@ -69,6 +69,9 @@ public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> imp
     @Autowired
     private PlainSchemaDAO schemaDAO;
 
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
     private String getAdminRolesFilter(final Set<Long> adminRoles, final SubjectType type) {
         final StringBuilder adminRolesFilter = new StringBuilder();
 
@@ -272,7 +275,7 @@ public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> imp
     private OrderBySupport parseOrderBy(final SubjectType type, final SearchSupport svs,
             final List<OrderByClause> orderByClauses) {
 
-        final AttributableUtil attrUtil = JPAAttributableUtil.getInstance(type.asAttributableType());
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
 
         OrderBySupport orderBySupport = new OrderBySupport();
 
@@ -598,7 +601,7 @@ public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> imp
     private String getQuery(final AttributeCond cond, final boolean not, final List<Object> parameters,
             final SubjectType type, final SearchSupport svs) {
 
-        final AttributableUtil attrUtil = JPAAttributableUtil.getInstance(type.asAttributableType());
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
 
         PlainSchema schema = schemaDAO.find(cond.getSchema(), attrUtil.plainSchemaClass());
         if (schema == null) {
@@ -647,7 +650,7 @@ public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> imp
     private String getQuery(final SubjectCond cond, final boolean not, final List<Object> parameters,
             final SubjectType type, final SearchSupport svs) {
 
-        final AttributableUtil attrUtil = JPAAttributableUtil.getInstance(type.asAttributableType());
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
 
         Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), cond.getSchema());
         if (subjectField == null) {
@@ -664,7 +667,7 @@ public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> imp
         }
 
         // Deal with subject Integer fields logically mapping to boolean values
-        // (SyncopeRole.inheritAttrs, for example)
+        // (SyncopeRole.inheritPlainAttrs, for example)
         boolean foundBooleanMin = false;
         boolean foundBooleanMax = false;
         if (Integer.class.equals(subjectField.getType())) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java
index 2c141a6..fd28fcd 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java
@@ -21,10 +21,13 @@ package org.apache.syncope.persistence.jpa.dao;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import javax.annotation.Resource;
 import javax.persistence.NoResultException;
 import javax.persistence.TypedQuery;
 import org.apache.syncope.common.lib.types.AttributableType;
 import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
 import org.apache.syncope.persistence.api.dao.RoleDAO;
 import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
 import org.apache.syncope.persistence.api.dao.UserDAO;
@@ -32,6 +35,7 @@ import org.apache.syncope.persistence.api.dao.search.AttributeCond;
 import org.apache.syncope.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.persistence.api.dao.search.SubjectCond;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
 import org.apache.syncope.persistence.api.entity.ExternalResource;
 import org.apache.syncope.persistence.api.entity.Subject;
 import org.apache.syncope.persistence.api.entity.VirAttr;
@@ -42,8 +46,9 @@ import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
 import org.apache.syncope.persistence.api.entity.user.UPlainAttrValue;
 import org.apache.syncope.persistence.api.entity.user.UVirAttr;
 import org.apache.syncope.persistence.api.entity.user.User;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.apache.syncope.persistence.jpa.entity.user.JPAUser;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.apache.syncope.server.security.UnauthorizedRoleException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 
@@ -56,6 +61,12 @@ public class JPAUserDAO extends AbstractSubjectDAO<UPlainAttr, UDerAttr, UVirAtt
     @Autowired
     private RoleDAO roleDAO;
 
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
     @Override
     protected Subject<UPlainAttr, UDerAttr, UVirAttr> findInternal(Long key) {
         return find(key);
@@ -138,27 +149,27 @@ public class JPAUserDAO extends AbstractSubjectDAO<UPlainAttr, UDerAttr, UVirAtt
     @Override
     public List<User> findByAttrValue(final String schemaName, final UPlainAttrValue attrValue) {
         return (List<User>) findByAttrValue(
-                schemaName, attrValue, JPAAttributableUtil.getInstance(AttributableType.USER));
+                schemaName, attrValue, attrUtilFactory.getInstance(AttributableType.USER));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public User findByAttrUniqueValue(final String schemaName, final UPlainAttrValue attrUniqueValue) {
         return (User) findByAttrUniqueValue(schemaName, attrUniqueValue,
-                JPAAttributableUtil.getInstance(AttributableType.USER));
+                attrUtilFactory.getInstance(AttributableType.USER));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public List<User> findByDerAttrValue(final String schemaName, final String value) {
         return (List<User>) findByDerAttrValue(
-                schemaName, value, JPAAttributableUtil.getInstance(AttributableType.USER));
+                schemaName, value, attrUtilFactory.getInstance(AttributableType.USER));
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public List<User> findByResource(final ExternalResource resource) {
-        return (List<User>) findByResource(resource, JPAAttributableUtil.getInstance(AttributableType.USER));
+        return (List<User>) findByResource(resource, attrUtilFactory.getInstance(AttributableType.USER));
     }
 
     @Override
@@ -222,4 +233,52 @@ public class JPAUserDAO extends AbstractSubjectDAO<UPlainAttr, UDerAttr, UVirAtt
 
         entityManager.remove(user);
     }
+
+    private void securityChecks(final User user) {
+        // Allows anonymous (during self-registration) and self (during self-update) to read own SyncopeUser,
+        // otherwise goes thorugh security checks to see if needed role entitlements are owned
+        if (!AuthContextUtil.getAuthenticatedUsername().equals(anonymousUser)
+                && !AuthContextUtil.getAuthenticatedUsername().equals(user.getUsername())) {
+
+            Set<Long> roleIds = user.getRoleIds();
+            Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+            roleIds.removeAll(adminRoleIds);
+            if (!roleIds.isEmpty()) {
+                throw new UnauthorizedRoleException(roleIds);
+            }
+        }
+    }
+
+    @Override
+    public User authFecthUser(final Long key) {
+        if (key == null) {
+            throw new NotFoundException("Null user id");
+        }
+
+        User user = find(key);
+        if (user == null) {
+            throw new NotFoundException("User " + key);
+        }
+
+        securityChecks(user);
+
+        return user;
+    }
+
+    @Override
+    public User authFecthUser(final String username) {
+        if (username == null) {
+            throw new NotFoundException("Null username");
+        }
+
+        User user = find(username);
+        if (user == null) {
+            throw new NotFoundException("User " + username);
+        }
+
+        securityChecks(user);
+
+        return user;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java
index 720b62b..8bde455 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.persistence.jpa.entity;
 
 import java.beans.PropertyDescriptor;
-import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.HashSet;
@@ -31,7 +30,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 
-public abstract class AbstractEntity<KEY> implements Entity<KEY>, Serializable {
+public abstract class AbstractEntity<KEY> implements Entity<KEY> {
 
     private static final long serialVersionUID = -9017214159540857901L;
 
@@ -41,7 +40,7 @@ public abstract class AbstractEntity<KEY> implements Entity<KEY>, Serializable {
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractEntity.class);
 
     protected void checkType(final Object object, final Class<?> clazz) {
-        if (object !=null && !clazz.isInstance(object)) {
+        if (object != null && !clazz.isInstance(object)) {
             throw new ClassCastException("Expected " + clazz.getName() + ", got " + object.getClass().getName());
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAAttributableUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAAttributableUtil.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAAttributableUtil.java
index 2705285..8b42b17 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAAttributableUtil.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAAttributableUtil.java
@@ -21,6 +21,12 @@ package org.apache.syncope.persistence.jpa.entity;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.ConfTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AttributableType;
 import org.apache.syncope.common.lib.types.IntMappingType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -36,10 +42,6 @@ import org.apache.syncope.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.persistence.api.entity.PlainSchema;
 import org.apache.syncope.persistence.api.entity.VirAttr;
 import org.apache.syncope.persistence.api.entity.VirSchema;
-import org.apache.syncope.persistence.api.entity.conf.Conf;
-import org.apache.syncope.persistence.api.entity.membership.Membership;
-import org.apache.syncope.persistence.api.entity.role.Role;
-import org.apache.syncope.persistence.api.entity.user.User;
 import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttr;
 import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttrUniqueValue;
 import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttrValue;
@@ -81,7 +83,6 @@ import org.apache.syncope.persistence.jpa.entity.user.JPAUVirAttr;
 import org.apache.syncope.persistence.jpa.entity.user.JPAUVirSchema;
 import org.apache.syncope.persistence.jpa.entity.user.JPAUser;
 import org.apache.syncope.server.spring.BeanUtils;
-import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.LoggerFactory;
 
@@ -93,55 +94,9 @@ public class JPAAttributableUtil implements AttributableUtil {
      */
     private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AttributableUtil.class);
 
-    public static AttributableUtil getInstance(final AttributableType type) {
-        return new JPAAttributableUtil(type);
-    }
-
-    public static AttributableUtil valueOf(final String name) {
-        return new JPAAttributableUtil(AttributableType.valueOf(name));
-    }
-
-    public static AttributableUtil getInstance(final ObjectClass objectClass) {
-        AttributableType type = null;
-        if (ObjectClass.ACCOUNT.equals(objectClass)) {
-            type = AttributableType.USER;
-        }
-        if (ObjectClass.GROUP.equals(objectClass)) {
-            type = AttributableType.ROLE;
-        }
-
-        if (type == null) {
-            throw new IllegalArgumentException("ObjectClass not supported: " + objectClass);
-        }
-
-        return new JPAAttributableUtil(type);
-    }
-
-    public static AttributableUtil getInstance(final Attributable attributable) {
-        AttributableType type = null;
-        if (attributable instanceof User) {
-            type = AttributableType.USER;
-        }
-        if (attributable instanceof Role) {
-            type = AttributableType.ROLE;
-        }
-        if (attributable instanceof Membership) {
-            type = AttributableType.MEMBERSHIP;
-        }
-        if (attributable instanceof Conf) {
-            type = AttributableType.CONFIGURATION;
-        }
-
-        if (type == null) {
-            throw new IllegalArgumentException("Attributable type not supported: " + attributable.getClass().getName());
-        }
-
-        return new JPAAttributableUtil(type);
-    }
-
     private final AttributableType type;
 
-    private JPAAttributableUtil(final AttributableType type) {
+    protected JPAAttributableUtil(final AttributableType type) {
         this.type = type;
     }
 
@@ -723,7 +678,7 @@ public class JPAAttributableUtil implements AttributableUtil {
             }
         }
 
-        final List<T> result = new ArrayList<T>();
+        final List<T> result = new ArrayList<>();
 
         switch (purpose) {
             case SYNCHRONIZATION:
@@ -879,4 +834,46 @@ public class JPAAttributableUtil implements AttributableUtil {
         return result;
     }
 
+    @Override
+    public <T extends AbstractAttributableTO> T newAttributableTO() {
+        T result = null;
+
+        switch (type) {
+            case USER:
+                result = (T) new UserTO();
+                break;
+            case ROLE:
+                result = (T) new RoleTO();
+                break;
+            case MEMBERSHIP:
+                result = (T) new MembershipTO();
+                break;
+            case CONFIGURATION:
+                result = (T) new ConfTO();
+                break;
+            default:
+        }
+
+        return result;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> T newSubjectTO() {
+        T result = null;
+
+        switch (type) {
+            case USER:
+                result = (T) new UserTO();
+                break;
+            case ROLE:
+                result = (T) new RoleTO();
+                break;
+            case MEMBERSHIP:
+            case CONFIGURATION:
+            default:
+                break;
+        }
+
+        return result;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAEntityFactory.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAEntityFactory.java
index c404c53..8e772b8 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAEntityFactory.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAEntityFactory.java
@@ -20,6 +20,7 @@ package org.apache.syncope.persistence.jpa.entity;
 
 import org.apache.syncope.persistence.api.entity.AccountPolicy;
 import org.apache.syncope.persistence.api.entity.ConnInstance;
+import org.apache.syncope.persistence.api.entity.ConnPoolConf;
 import org.apache.syncope.persistence.api.entity.Entitlement;
 import org.apache.syncope.persistence.api.entity.Entity;
 import org.apache.syncope.persistence.api.entity.EntityFactory;
@@ -134,8 +135,8 @@ import org.springframework.stereotype.Component;
 @Component
 public class JPAEntityFactory implements EntityFactory {
 
-    @Override
     @SuppressWarnings("unchecked")
+    @Override
     public <KEY, T extends Entity<KEY>> T newEntity(final Class<T> reference) {
         T result;
 
@@ -258,6 +259,7 @@ public class JPAEntityFactory implements EntityFactory {
         return result;
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public <T extends Policy> T newPolicy(final Class<T> reference, final boolean global) {
         T result;
@@ -277,4 +279,9 @@ public class JPAEntityFactory implements EntityFactory {
         return result;
     }
 
+    @Override
+    public ConnPoolConf newConnPoolConf() {
+        return new JPAConnPoolConf();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAPushPolicy.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAPushPolicy.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAPushPolicy.java
index 1a661a9..b221330 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAPushPolicy.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAPushPolicy.java
@@ -25,7 +25,7 @@ import org.apache.syncope.persistence.api.entity.PushPolicy;
 @Entity
 public class JPAPushPolicy extends JPAPolicy implements PushPolicy {
 
-    private static final long serialVersionUID = -6090413855809521279L;
+    private static final long serialVersionUID = -5875589156893921113L;
 
     public JPAPushPolicy() {
         this(false);

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPASecurityQuestion.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPASecurityQuestion.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPASecurityQuestion.java
index bfe76bc..4ce7d5e 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPASecurityQuestion.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPASecurityQuestion.java
@@ -28,7 +28,7 @@ import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
 @Table(name = JPASecurityQuestion.TABLE)
 public class JPASecurityQuestion extends AbstractEntity<Long> implements SecurityQuestion {
 
-    private static final long serialVersionUID = -7646140284033489392L;
+    private static final long serialVersionUID = 7675321820453579744L;
 
     public static final String TABLE = "SecurityQuestion";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAttributableUtilFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAttributableUtilFactory.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAttributableUtilFactory.java
new file mode 100644
index 0000000..6962b41
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/JPAttributableUtilFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.persistence.jpa.entity;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.conf.Conf;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JPAttributableUtilFactory implements AttributableUtilFactory {
+
+    @Override
+    public AttributableUtil getInstance(final AttributableType type) {
+        return new JPAAttributableUtil(type);
+    }
+
+    @Override
+    public AttributableUtil getInstance(final String attributableType) {
+        return new JPAAttributableUtil(AttributableType.valueOf(attributableType));
+    }
+
+    @Override
+    public AttributableUtil getInstance(final ObjectClass objectClass) {
+        AttributableType type = null;
+        if (ObjectClass.ACCOUNT.equals(objectClass)) {
+            type = AttributableType.USER;
+        }
+        if (ObjectClass.GROUP.equals(objectClass)) {
+            type = AttributableType.ROLE;
+        }
+
+        if (type == null) {
+            throw new IllegalArgumentException("ObjectClass not supported: " + objectClass);
+        }
+
+        return new JPAAttributableUtil(type);
+    }
+
+    @Override
+    public AttributableUtil getInstance(final Attributable<?, ?, ?> attributable) {
+        AttributableType type = null;
+        if (attributable instanceof User) {
+            type = AttributableType.USER;
+        }
+        if (attributable instanceof Role) {
+            type = AttributableType.ROLE;
+        }
+        if (attributable instanceof Membership) {
+            type = AttributableType.MEMBERSHIP;
+        }
+        if (attributable instanceof Conf) {
+            type = AttributableType.CONFIGURATION;
+        }
+
+        if (type == null) {
+            throw new IllegalArgumentException("Attributable type not supported: " + attributable.getClass().getName());
+        }
+
+        return new JPAAttributableUtil(type);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttr.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttr.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttr.java
index 74c7d66..3e0d404 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttr.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttr.java
@@ -48,7 +48,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractPlainAttr;
 @Table(name = JPACPlainAttr.TABLE)
 public class JPACPlainAttr extends AbstractPlainAttr implements CPlainAttr {
 
-    private static final long serialVersionUID = 6333601983691157406L;
+    private static final long serialVersionUID = 8022331942314540648L;
 
     public static final String TABLE = "CPlainAttr";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrUniqueValue.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrUniqueValue.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrUniqueValue.java
index 13e8e04..8cc830c 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrUniqueValue.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrUniqueValue.java
@@ -35,7 +35,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractPlainAttrValue;
 @Table(name = JPACPlainAttrUniqueValue.TABLE)
 public class JPACPlainAttrUniqueValue extends AbstractPlainAttrValue implements CPlainAttrUniqueValue {
 
-    private static final long serialVersionUID = -64080804563305387L;
+    private static final long serialVersionUID = -2072445894710677162L;
 
     public static final String TABLE = "CPlainAttrUniqueValue";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrValue.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrValue.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrValue.java
index 70e30f3..ff0a8ae 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrValue.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPACPlainAttrValue.java
@@ -35,7 +35,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractPlainAttrValue;
 @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
 public class JPACPlainAttrValue extends AbstractPlainAttrValue implements CPlainAttrValue {
 
-    private static final long serialVersionUID = -6259576015647897446L;
+    private static final long serialVersionUID = -4029895248193486171L;
 
     public static final String TABLE = "CPlainAttrValue";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPAConf.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPAConf.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPAConf.java
index 3bfa884..0438543 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPAConf.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/conf/JPAConf.java
@@ -39,7 +39,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractAttributable;
 @Cacheable
 public class JPAConf extends AbstractAttributable<CPlainAttr, DerAttr, VirAttr> implements Conf {
 
-    private static final long serialVersionUID = -5281258853142421875L;
+    private static final long serialVersionUID = 7671699609879382195L;
 
     public static final String TABLE = "SyncopeConf";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMDerAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMDerAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMDerAttrTemplate.java
index 7ec7fcf..1b35470 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMDerAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMDerAttrTemplate.java
@@ -32,7 +32,7 @@ import org.apache.syncope.persistence.jpa.entity.role.JPARole;
 @Table(name = JPAMDerAttrTemplate.TABLE)
 public class JPAMDerAttrTemplate extends AbstractAttrTemplate<MDerSchema> implements MDerAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = -4465930976210263434L;
 
     public static final String TABLE = "MDerAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMPlainAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMPlainAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMPlainAttrTemplate.java
index fb8e728..ad6d2c2 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMPlainAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMPlainAttrTemplate.java
@@ -32,7 +32,7 @@ import org.apache.syncope.persistence.jpa.entity.role.JPARole;
 @Table(name = JPAMPlainAttrTemplate.TABLE)
 public class JPAMPlainAttrTemplate extends AbstractAttrTemplate<MPlainSchema> implements MPlainAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = -8768086609963244514L;
 
     public static final String TABLE = "MPlainAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMVirAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMVirAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMVirAttrTemplate.java
index 7bd60db..6840686 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMVirAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/membership/JPAMVirAttrTemplate.java
@@ -32,7 +32,7 @@ import org.apache.syncope.persistence.jpa.entity.role.JPARole;
 @Table(name = JPAMVirAttrTemplate.TABLE)
 public class JPAMVirAttrTemplate extends AbstractAttrTemplate<MVirSchema> implements MVirAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = 6618560912535667392L;
 
     public static final String TABLE = "MVirAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARDerAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARDerAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARDerAttrTemplate.java
index ba565a7..3b382bb 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARDerAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARDerAttrTemplate.java
@@ -31,7 +31,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractAttrTemplate;
 @Table(name = JPARDerAttrTemplate.TABLE)
 public class JPARDerAttrTemplate extends AbstractAttrTemplate<RDerSchema> implements RDerAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = 624868884107016649L;
 
     public static final String TABLE = "RDerAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARPlainAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARPlainAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARPlainAttrTemplate.java
index 90ac153..7d88bf7 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARPlainAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARPlainAttrTemplate.java
@@ -31,7 +31,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractAttrTemplate;
 @Table(name = JPARPlainAttrTemplate.TABLE)
 public class JPARPlainAttrTemplate extends AbstractAttrTemplate<RPlainSchema> implements RPlainAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = 6943917051517266268L;
 
     public static final String TABLE = "RPlainAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARVirAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARVirAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARVirAttrTemplate.java
index a5e41bd..2228663 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARVirAttrTemplate.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARVirAttrTemplate.java
@@ -31,7 +31,7 @@ import org.apache.syncope.persistence.jpa.entity.AbstractAttrTemplate;
 @Table(name = JPARVirAttrTemplate.TABLE)
 public class JPARVirAttrTemplate extends AbstractAttrTemplate<RVirSchema> implements RVirAttrTemplate {
 
-    private static final long serialVersionUID = -3424574558427502145L;
+    private static final long serialVersionUID = 4896495904794493479L;
 
     public static final String TABLE = "RVirAttrTemplate";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARole.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARole.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARole.java
index 05c9e9f..50bcf42 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARole.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/role/JPARole.java
@@ -156,7 +156,7 @@ public class JPARole extends AbstractSubject<RPlainAttr, RDerAttr, RVirAttr> imp
     @Basic(optional = true)
     @Min(0)
     @Max(1)
-    private Integer inheritAttrs;
+    private Integer inheritPlainAttrs;
 
     @Basic(optional = true)
     @Min(0)
@@ -213,7 +213,7 @@ public class JPARole extends AbstractSubject<RPlainAttr, RDerAttr, RVirAttr> imp
 
         inheritOwner = getBooleanAsInteger(false);
         inheritTemplates = getBooleanAsInteger(false);
-        inheritAttrs = getBooleanAsInteger(false);
+        inheritPlainAttrs = getBooleanAsInteger(false);
         inheritDerAttrs = getBooleanAsInteger(false);
         inheritVirAttrs = getBooleanAsInteger(false);
         inheritPasswordPolicy = getBooleanAsInteger(false);
@@ -423,13 +423,13 @@ public class JPARole extends AbstractSubject<RPlainAttr, RDerAttr, RVirAttr> imp
     }
 
     @Override
-    public boolean isInheritAttrs() {
-        return isBooleanAsInteger(inheritAttrs);
+    public boolean isInheritPlainAttrs() {
+        return isBooleanAsInteger(inheritPlainAttrs);
     }
 
     @Override
-    public void setInheritAttrs(final boolean inheritAttrs) {
-        this.inheritAttrs = getBooleanAsInteger(inheritAttrs);
+    public void setInheritPlainAttrs(final boolean inheritPlainAttrs) {
+        this.inheritPlainAttrs = getBooleanAsInteger(inheritPlainAttrs);
     }
 
     /**
@@ -441,10 +441,10 @@ public class JPARole extends AbstractSubject<RPlainAttr, RDerAttr, RVirAttr> imp
     public List<? extends RPlainAttr> findLastInheritedAncestorPlainAttrs() {
         final Map<JPARPlainSchema, RPlainAttr> result = new HashMap<>();
 
-        if (!isInheritAttrs()) {
+        if (!isInheritPlainAttrs()) {
             return plainAttrs;
         }
-        if (isInheritAttrs() && getParent() != null) {
+        if (isInheritPlainAttrs() && getParent() != null) {
             final Map<PlainSchema, RPlainAttr> attrMap = getPlainAttrMap();
 
             // Add inherit attributes

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPASchedTask.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPASchedTask.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPASchedTask.java
index 649664d..ef03fd7 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPASchedTask.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPASchedTask.java
@@ -30,7 +30,7 @@ import org.apache.syncope.persistence.jpa.validation.entity.SchedTaskCheck;
 @SchedTaskCheck
 public class JPASchedTask extends JPATask implements SchedTask {
 
-    private static final long serialVersionUID = -4141057723006682562L;
+    private static final long serialVersionUID = 7596236684832602180L;
 
     protected String cronExpression;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtil.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtil.java
new file mode 100644
index 0000000..3fae881
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtil.java
@@ -0,0 +1,132 @@
+/*
+ * 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.persistence.jpa.entity.task;
+
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.NotificationTaskTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.PushTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskUtil;
+
+@SuppressWarnings("unchecked")
+public final class JPATaskUtil implements TaskUtil {
+
+    private final TaskType type;
+
+    protected JPATaskUtil(final TaskType type) {
+        this.type = type;
+    }
+
+    @Override
+    public TaskType getType() {
+        return type;
+    }
+
+    @Override
+    public <T extends Task> Class<T> taskClass() {
+        Class<T> result = null;
+
+        switch (type) {
+            case PROPAGATION:
+                result = (Class<T>) PropagationTask.class;
+                break;
+
+            case SCHEDULED:
+                result = (Class<T>) SchedTask.class;
+                break;
+
+            case SYNCHRONIZATION:
+                result = (Class<T>) SyncTask.class;
+                break;
+
+            case PUSH:
+                result = (Class<T>) PushTask.class;
+                break;
+
+            case NOTIFICATION:
+                result = (Class<T>) NotificationTask.class;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    @Override
+    public <T extends Task> T newTask() {
+        final Class<T> taskClass = taskClass();
+        try {
+            return taskClass == null ? null : taskClass.newInstance();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public <T extends AbstractTaskTO> Class<T> taskTOClass() {
+        Class<T> result = null;
+
+        switch (type) {
+            case PROPAGATION:
+                result = (Class<T>) PropagationTaskTO.class;
+                break;
+
+            case SCHEDULED:
+                result = (Class<T>) SchedTaskTO.class;
+                break;
+
+            case SYNCHRONIZATION:
+                result = (Class<T>) SyncTaskTO.class;
+                break;
+
+            case PUSH:
+                result = (Class<T>) PushTaskTO.class;
+                break;
+
+            case NOTIFICATION:
+                result = (Class<T>) NotificationTaskTO.class;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends AbstractTaskTO> T newTaskTO() {
+        final Class<T> taskClass = taskTOClass();
+        try {
+            return taskClass == null ? null : taskClass.newInstance();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtilFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtilFactory.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtilFactory.java
new file mode 100644
index 0000000..62f5dab
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/task/JPATaskUtilFactory.java
@@ -0,0 +1,91 @@
+/*
+ * 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.persistence.jpa.entity.task;
+
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.NotificationTaskTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.task.PushTask;
+import org.apache.syncope.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskUtil;
+import org.apache.syncope.persistence.api.entity.task.TaskUtilFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JPATaskUtilFactory implements TaskUtilFactory {
+
+    @Override
+    public TaskUtil getInstance(final TaskType type) {
+        return new JPATaskUtil(type);
+    }
+
+    @Override
+    public TaskUtil getInstance(final Task task) {
+        TaskType type;
+        if (task instanceof SyncTask) {
+            type = TaskType.SYNCHRONIZATION;
+        } else if (task instanceof PushTask) {
+            type = TaskType.PUSH;
+        } else if (task instanceof SchedTask) {
+            type = TaskType.SCHEDULED;
+        } else if (task instanceof PropagationTask) {
+            type = TaskType.PROPAGATION;
+        } else if (task instanceof NotificationTask) {
+            type = TaskType.NOTIFICATION;
+        } else {
+            throw new IllegalArgumentException("Invalid task: " + task);
+        }
+
+        return getInstance(type);
+    }
+
+    @Override
+    public TaskUtil getInstance(final Class<? extends AbstractTaskTO> taskClass) {
+        TaskType type;
+        if (taskClass == PropagationTaskTO.class) {
+            type = TaskType.PROPAGATION;
+        } else if (taskClass == NotificationTaskTO.class) {
+            type = TaskType.NOTIFICATION;
+        } else if (taskClass == SchedTaskTO.class) {
+            type = TaskType.SCHEDULED;
+        } else if (taskClass == SyncTaskTO.class) {
+            type = TaskType.SYNCHRONIZATION;
+        } else if (taskClass == PushTaskTO.class) {
+            type = TaskType.PUSH;
+        } else {
+            throw new IllegalArgumentException("Invalid TaskTO class: " + taskClass.getName());
+        }
+
+        return getInstance(type);
+    }
+
+    @Override
+    public TaskUtil getInstance(final AbstractTaskTO taskTO) {
+        return getInstance(taskTO.getClass());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/user/JPAUser.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/user/JPAUser.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/user/JPAUser.java
index ec28d6c..efc66ec 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/user/JPAUser.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/user/JPAUser.java
@@ -62,9 +62,10 @@ import org.apache.syncope.persistence.api.entity.user.User;
 import org.apache.syncope.persistence.jpa.validation.entity.UserCheck;
 import org.apache.syncope.persistence.jpa.entity.AbstractSubject;
 import org.apache.syncope.persistence.jpa.entity.JPAExternalResource;
+import org.apache.syncope.persistence.jpa.entity.JPASecurityQuestion;
 import org.apache.syncope.persistence.jpa.entity.membership.JPAMembership;
 import org.apache.syncope.server.security.Encryptor;
-import org.apache.syncope.server.security.SecureRandomUtil;
+import org.apache.syncope.server.utils.SecureRandomUtil;
 
 /**
  * Syncope user bean.
@@ -170,7 +171,7 @@ public class JPAUser extends AbstractSubject<UPlainAttr, UDerAttr, UVirAttr> imp
     private Set<JPAExternalResource> resources;
 
     @ManyToOne(fetch = FetchType.EAGER, optional = true)
-    private SecurityQuestion securityQuestion;
+    private JPASecurityQuestion securityQuestion;
 
     @Column(nullable = true)
     private String securityAnswer;
@@ -520,7 +521,8 @@ public class JPAUser extends AbstractSubject<UPlainAttr, UDerAttr, UVirAttr> imp
 
     @Override
     public void setSecurityQuestion(final SecurityQuestion securityQuestion) {
-        this.securityQuestion = securityQuestion;
+        checkType(securityQuestion, JPASecurityQuestion.class);
+        this.securityQuestion = (JPASecurityQuestion) securityQuestion;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/validation/entity/ConnInstanceValidator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/validation/entity/ConnInstanceValidator.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/validation/entity/ConnInstanceValidator.java
index ba60ae4..c2bdb3a 100644
--- a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/validation/entity/ConnInstanceValidator.java
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/validation/entity/ConnInstanceValidator.java
@@ -22,7 +22,7 @@ import javax.validation.ConstraintValidatorContext;
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.persistence.api.entity.ConnInstance;
 import org.apache.syncope.persistence.jpa.entity.JPAConnPoolConf;
-import org.apache.syncope.server.utils.URIUtil;
+import org.apache.syncope.provisioning.api.URIUtil;
 import org.apache.syncope.provisioning.api.ConnPoolConfUtil;
 
 public class ConnInstanceValidator extends AbstractValidator<ConnInstanceCheck, ConnInstance> {

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/AbstractTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/AbstractTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/AbstractTest.java
index 2c78453..afb405a 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/AbstractTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/AbstractTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.persistence.jpa;
 
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
 import org.apache.syncope.persistence.api.entity.EntityFactory;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,4 +32,7 @@ public abstract class AbstractTest {
     @Autowired
     protected EntityFactory entityFactory;
 
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/AttrTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/AttrTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/AttrTest.java
index f751b77..0b5ccf1 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/AttrTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/AttrTest.java
@@ -87,15 +87,15 @@ public class AttrTest extends AbstractTest {
 
         Exception thrown = null;
         try {
-            attribute.addValue("john.doe@gmail.com", JPAAttributableUtil.getInstance(AttributableType.USER));
-            attribute.addValue("mario.rossi@gmail.com", JPAAttributableUtil.getInstance(AttributableType.USER));
+            attribute.addValue("john.doe@gmail.com", attrUtilFactory.getInstance(AttributableType.USER));
+            attribute.addValue("mario.rossi@gmail.com", attrUtilFactory.getInstance(AttributableType.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNull("no validation exception expected here ", thrown);
 
         try {
-            attribute.addValue("http://www.apache.org", JPAAttributableUtil.getInstance(AttributableType.USER));
+            attribute.addValue("http://www.apache.org", attrUtilFactory.getInstance(AttributableType.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
@@ -119,13 +119,13 @@ public class AttrTest extends AbstractTest {
         Exception thrown = null;
 
         try {
-            attribute.addValue("A", JPAAttributableUtil.getInstance(AttributableType.USER));
+            attribute.addValue("A", attrUtilFactory.getInstance(AttributableType.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNotNull("validation exception expected here ", thrown);
 
-        attribute.addValue("M", JPAAttributableUtil.getInstance(AttributableType.USER));
+        attribute.addValue("M", attrUtilFactory.getInstance(AttributableType.USER));
 
         InvalidEntityException iee = null;
         try {
@@ -183,7 +183,7 @@ public class AttrTest extends AbstractTest {
 
         UPlainAttr attribute = entityFactory.newEntity(UPlainAttr.class);
         attribute.setSchema(obscureSchema);
-        attribute.addValue("testvalue", JPAAttributableUtil.getInstance(AttributableType.USER));
+        attribute.addValue("testvalue", attrUtilFactory.getInstance(AttributableType.USER));
         attribute.setOwner(user);
         user.addPlainAttr(attribute);
 
@@ -210,7 +210,7 @@ public class AttrTest extends AbstractTest {
 
         UPlainAttr attribute = entityFactory.newEntity(UPlainAttr.class);
         attribute.setSchema(photoSchema);
-        attribute.addValue(photoB64Value, JPAAttributableUtil.getInstance(AttributableType.USER));
+        attribute.addValue(photoB64Value, attrUtilFactory.getInstance(AttributableType.USER));
         attribute.setOwner(user);
         user.addPlainAttr(attribute);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/ConfTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/ConfTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/ConfTest.java
index ab662b7..143a4fe 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/ConfTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/ConfTest.java
@@ -72,7 +72,7 @@ public class ConfTest extends AbstractTest {
         // 2. create conf
         CPlainAttr newConf = entityFactory.newEntity(CPlainAttr.class);
         newConf.setSchema(useless);
-        newConf.addValue("2014-06-20", JPAAttributableUtil.getInstance(AttributableType.CONFIGURATION));
+        newConf.addValue("2014-06-20", attrUtilFactory.getInstance(AttributableType.CONFIGURATION));
         confDAO.save(newConf);
 
         CPlainAttr actual = confDAO.find("useless");
@@ -80,7 +80,7 @@ public class ConfTest extends AbstractTest {
 
         // 3. update conf
         newConf.getValues().clear();
-        newConf.addValue("2014-06-20", JPAAttributableUtil.getInstance(AttributableType.CONFIGURATION));
+        newConf.addValue("2014-06-20", attrUtilFactory.getInstance(AttributableType.CONFIGURATION));
         confDAO.save(newConf);
 
         actual = confDAO.find("useless");

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/DerSchemaTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/DerSchemaTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/DerSchemaTest.java
index 19af5e9..94c61aa 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/DerSchemaTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/DerSchemaTest.java
@@ -73,7 +73,7 @@ public class DerSchemaTest extends AbstractTest {
         UDerSchema cn = derSchemaDAO.find("cn", UDerSchema.class);
         assertNotNull(cn);
 
-        derSchemaDAO.delete(cn.getKey(), JPAAttributableUtil.getInstance(AttributableType.USER));
+        derSchemaDAO.delete(cn.getKey(), attrUtilFactory.getInstance(AttributableType.USER));
 
         DerSchema actual = derSchemaDAO.find("cn", UDerSchema.class);
         assertNull("delete did not work", actual);
@@ -82,7 +82,7 @@ public class DerSchemaTest extends AbstractTest {
         RDerSchema rderiveddata = derSchemaDAO.find("rderiveddata", RDerSchema.class);
         assertNotNull(rderiveddata);
 
-        derSchemaDAO.delete(rderiveddata.getKey(), JPAAttributableUtil.getInstance(AttributableType.ROLE));
+        derSchemaDAO.delete(rderiveddata.getKey(), attrUtilFactory.getInstance(AttributableType.ROLE));
 
         actual = derSchemaDAO.find("rderiveddata", RDerSchema.class);
         assertNull("delete did not work", actual);

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/PlainSchemaTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/PlainSchemaTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/PlainSchemaTest.java
index 4e47087..652d32e 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/PlainSchemaTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/PlainSchemaTest.java
@@ -139,7 +139,7 @@ public class PlainSchemaTest extends AbstractTest {
     public void delete() {
         UPlainSchema fullnam = plainSchemaDAO.find("fullname", UPlainSchema.class);
 
-        plainSchemaDAO.delete(fullnam.getKey(), JPAAttributableUtil.getInstance(AttributableType.USER));
+        plainSchemaDAO.delete(fullnam.getKey(), attrUtilFactory.getInstance(AttributableType.USER));
 
         UPlainSchema actual = plainSchemaDAO.find("fullname", UPlainSchema.class);
         assertNull("delete did not work", actual);

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/VirSchemaTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/VirSchemaTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/VirSchemaTest.java
index 9ae9e48..df60909 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/VirSchemaTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/entity/VirSchemaTest.java
@@ -72,7 +72,7 @@ public class VirSchemaTest extends AbstractTest {
     public void delete() {
         UVirSchema virtualdata = virSchemaDAO.find("virtualdata", UVirSchema.class);
 
-        virSchemaDAO.delete(virtualdata.getKey(), JPAAttributableUtil.getInstance(AttributableType.USER));
+        virSchemaDAO.delete(virtualdata.getKey(), attrUtilFactory.getInstance(AttributableType.USER));
 
         VirSchema actual = virSchemaDAO.find("virtualdata", UVirSchema.class);
         assertNull("delete did not work", actual);
@@ -81,7 +81,7 @@ public class VirSchemaTest extends AbstractTest {
         RVirSchema rvirtualdata = virSchemaDAO.find("rvirtualdata", RVirSchema.class);
         assertNotNull(rvirtualdata);
 
-        virSchemaDAO.delete(rvirtualdata.getKey(), JPAAttributableUtil.getInstance(AttributableType.ROLE));
+        virSchemaDAO.delete(rvirtualdata.getKey(), attrUtilFactory.getInstance(AttributableType.ROLE));
 
         actual = virSchemaDAO.find("rvirtualdata", RVirSchema.class);
         assertNull("delete did not work", actual);

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/AttrTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/AttrTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/AttrTest.java
index 1074726..a74e3a1 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/AttrTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/AttrTest.java
@@ -47,7 +47,6 @@ import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
 import org.apache.syncope.persistence.api.entity.user.UPlainAttrValue;
 import org.apache.syncope.persistence.api.entity.user.User;
 import org.apache.syncope.persistence.jpa.AbstractTest;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -125,7 +124,7 @@ public class AttrTest extends AbstractTest {
         MPlainAttr attr = entityFactory.newEntity(MPlainAttr.class);
         attr.setTemplate(template);
         attr.setOwner(membership);
-        attr.addValue("yellow", JPAAttributableUtil.getInstance(AttributableType.MEMBERSHIP));
+        attr.addValue("yellow", attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
         membership.addPlainAttr(attr);
 
         MPlainAttr actualAttribute = userDAO.save(user).getMembership(1L).getPlainAttr("color");

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/DerSchemaTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/DerSchemaTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/DerSchemaTest.java
index 685c2e2..2982423 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/DerSchemaTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/DerSchemaTest.java
@@ -27,7 +27,6 @@ import org.apache.syncope.persistence.api.dao.UserDAO;
 import org.apache.syncope.persistence.api.entity.user.UDerAttr;
 import org.apache.syncope.persistence.api.entity.user.UDerSchema;
 import org.apache.syncope.persistence.jpa.AbstractTest;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -48,7 +47,7 @@ public class DerSchemaTest extends AbstractTest {
     public void test() {
         UDerSchema schema = derSchemaDAO.find("cn", UDerSchema.class);
 
-        derSchemaDAO.delete(schema.getKey(), JPAAttributableUtil.getInstance(AttributableType.USER));
+        derSchemaDAO.delete(schema.getKey(), attrUtilFactory.getInstance(AttributableType.USER));
 
         derSchemaDAO.flush();
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/PlainSchemaTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/PlainSchemaTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/PlainSchemaTest.java
index 29fb41e..710444c 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/PlainSchemaTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/PlainSchemaTest.java
@@ -36,7 +36,6 @@ import org.apache.syncope.persistence.api.entity.MappingItem;
 import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
 import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
 import org.apache.syncope.persistence.jpa.AbstractTest;
-import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -86,7 +85,7 @@ public class PlainSchemaTest extends AbstractTest {
         assertFalse(mapItems.isEmpty());
 
         // delete user schema fullname
-        plainSchemaDAO.delete("fullname", JPAAttributableUtil.getInstance(AttributableType.USER));
+        plainSchemaDAO.delete("fullname", attrUtilFactory.getInstance(AttributableType.USER));
 
         plainSchemaDAO.flush();
 
@@ -135,7 +134,7 @@ public class PlainSchemaTest extends AbstractTest {
         assertFalse(mappings.isEmpty());
 
         // delete user schema fullname
-        plainSchemaDAO.delete("surname", JPAAttributableUtil.getInstance(AttributableType.USER));
+        plainSchemaDAO.delete("surname", attrUtilFactory.getInstance(AttributableType.USER));
 
         plainSchemaDAO.flush();
 
@@ -148,7 +147,7 @@ public class PlainSchemaTest extends AbstractTest {
     public void deleteALong() {
         assertEquals(6, resourceDAO.find("resource-db-sync").getUmapping().getItems().size());
 
-        plainSchemaDAO.delete("aLong", JPAAttributableUtil.getInstance(AttributableType.USER));
+        plainSchemaDAO.delete("aLong", attrUtilFactory.getInstance(AttributableType.USER));
         assertNull(plainSchemaDAO.find("aLong", UPlainSchema.class));
 
         plainSchemaDAO.flush();

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/SecurityQuestionTest.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/SecurityQuestionTest.java b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/SecurityQuestionTest.java
index 8fcac93..e4231b7 100644
--- a/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/SecurityQuestionTest.java
+++ b/syncope620/server/persistence-jpa/src/test/java/org/apache/syncope/persistence/jpa/relationship/SecurityQuestionTest.java
@@ -40,6 +40,8 @@ public class SecurityQuestionTest extends AbstractTest {
     @Test
     public void test() {
         User user = userDAO.find(4L);
+        assertNull(user.getSecurityQuestion());
+        assertNull(user.getSecurityAnswer());
 
         user.setSecurityQuestion(securityQuestionDAO.find(1L));
         user.setSecurityAnswer("Rossi");
@@ -54,5 +56,6 @@ public class SecurityQuestionTest extends AbstractTest {
         user = userDAO.find(4L);
 
         assertNull(user.getSecurityQuestion());
+        assertNull(user.getSecurityAnswer());
     }
 }


[08/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
new file mode 100644
index 0000000..870a075
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
@@ -0,0 +1,534 @@
+/*
+ * 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.server.logic;
+
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.BulkActionResult.Status;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.AttributableTransformer;
+import org.apache.syncope.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+/**
+ * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
+ * Spring's Transactional logic at class level.
+ */
+@Component
+public class UserLogic extends AbstractSubjectLogic<UserTO, UserMod> {
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected SubjectSearchDAO searchDAO;
+
+    @Autowired
+    protected ConfDAO confDAO;
+
+    @Autowired
+    protected UserDataBinder binder;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected AttributableTransformer attrTransformer;
+
+    @Autowired
+    protected UserProvisioningManager provisioningManager;
+
+    @Transactional(readOnly = true)
+    public boolean isSelfRegAllowed() {
+        return confDAO.find("selfRegistration.allowed", "false").getValues().get(0).getBooleanValue();
+    }
+
+    @Transactional(readOnly = true)
+    public boolean isPwdResetAllowed() {
+        return confDAO.find("passwordReset.allowed", "false").getValues().get(0).getBooleanValue();
+    }
+
+    @Transactional(readOnly = true)
+    public boolean isPwdResetRequiringSecurityQuestions() {
+        return confDAO.find("passwordReset.securityQuestion", "true").getValues().get(0).getBooleanValue();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    public String getUsername(final Long key) {
+        return binder.getUserTO(key).getUsername();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    public Long getKey(final String username) {
+        return binder.getUserTO(username).getKey();
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int count() {
+        return userDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()));
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int searchCount(final SearchCond searchCondition) {
+        return searchDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, SubjectType.USER);
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public List<UserTO> list(final int page, final int size, final List<OrderByClause> orderBy) {
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+
+        List<User> users = userDAO.findAll(adminRoleIds, page, size, orderBy);
+        List<UserTO> userTOs = new ArrayList<UserTO>(users.size());
+        for (User user : users) {
+            userTOs.add(binder.getUserTO(user));
+        }
+
+        return userTOs;
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    @Transactional(readOnly = true)
+    public UserTO readSelf() {
+        return binder.getAuthenticatedUserTO();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO read(final Long key) {
+        return binder.getUserTO(key);
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true)
+    @Override
+    public List<UserTO> search(final SearchCond searchCondition, final int page, final int size,
+            final List<OrderByClause> orderBy) {
+
+        final List<User> matchingUsers = searchDAO.search(
+                RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, page, size, orderBy, SubjectType.USER);
+
+        final List<UserTO> result = new ArrayList<>(matchingUsers.size());
+        for (User user : matchingUsers) {
+            result.add(binder.getUserTO(user));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    public UserTO createSelf(final UserTO userTO, final boolean storePassword) {
+        return doCreate(userTO, storePassword);
+    }
+
+    @PreAuthorize("hasRole('USER_CREATE')")
+    public UserTO create(final UserTO userTO, final boolean storePassword) {
+        Set<Long> requestRoleIds = new HashSet<Long>(userTO.getMemberships().size());
+        for (MembershipTO membership : userTO.getMemberships()) {
+            requestRoleIds.add(membership.getRoleId());
+        }
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        requestRoleIds.removeAll(adminRoleIds);
+        if (!requestRoleIds.isEmpty()) {
+            throw new UnauthorizedRoleException(requestRoleIds);
+        }
+
+        return doCreate(userTO, storePassword);
+    }
+
+    protected UserTO doCreate(final UserTO userTO, final boolean storePassword) {
+        // Attributable transformation (if configured)
+        UserTO actual = attrTransformer.transform(userTO);
+        LOG.debug("Transformed: {}", actual);
+
+        Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(actual, storePassword);
+
+        final UserTO savedTO = binder.getUserTO(created.getKey());
+        savedTO.getPropagationStatusTOs().addAll(created.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    public UserTO updateSelf(final UserMod userMod) {
+        UserTO userTO = binder.getAuthenticatedUserTO();
+
+        if (userTO.getKey() != userMod.getKey()) {
+            throw new AccessControlException("Not allowed for user with key " + userMod.getKey());
+        }
+
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Override
+    public UserTO update(final UserMod userMod) {
+        // AttributableMod transformation (if configured)
+        UserMod actual = attrTransformer.transform(userMod);
+        LOG.debug("Transformed: {}", actual);
+
+        // SYNCOPE-501: check if there are memberships to be removed with virtual attributes assigned
+        boolean removeMemberships = false;
+        for (Long membershipId : actual.getMembershipsToRemove()) {
+            if (!binder.fillMembershipVirtual(
+                    null,
+                    null,
+                    membershipId,
+                    Collections.<String>emptySet(),
+                    Collections.<AttrMod>emptySet(),
+                    true).isEmpty()) {
+
+                removeMemberships = true;
+            }
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(actual, removeMemberships);
+
+        final UserTO updatedTO = binder.getUserTO(updated.getKey());
+        updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return updatedTO;
+    }
+
+    protected Map.Entry<Long, List<PropagationStatus>> setStatusOnWfAdapter(final User user,
+            final StatusMod statusMod) {
+        Map.Entry<Long, List<PropagationStatus>> updated;
+
+        switch (statusMod.getType()) {
+            case SUSPEND:
+                updated = provisioningManager.suspend(user, statusMod);
+                break;
+
+            case REACTIVATE:
+                updated = provisioningManager.reactivate(user, statusMod);
+                break;
+
+            case ACTIVATE:
+            default:
+                updated = provisioningManager.activate(user, statusMod);
+                break;
+
+        }
+
+        return updated;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public UserTO status(final StatusMod statusMod) {
+        User user = userDAO.authFecthUser(statusMod.getId());
+
+        Map.Entry<Long, List<PropagationStatus>> updated = setStatusOnWfAdapter(user, statusMod);
+        final UserTO savedTO = binder.getUserTO(updated.getKey());
+        savedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional
+    public void requestPasswordReset(final String username, final String securityAnswer) {
+        if (username == null) {
+            throw new NotFoundException("Null username");
+        }
+
+        User user = userDAO.find(username);
+        if (user == null) {
+            throw new NotFoundException("User " + username);
+        }
+
+        if (isPwdResetRequiringSecurityQuestions()
+                && (securityAnswer == null || !securityAnswer.equals(user.getSecurityAnswer()))) {
+
+            throw SyncopeClientException.build(ClientExceptionType.InvalidSecurityAnswer);
+        }
+
+        provisioningManager.requestPasswordReset(user.getKey());
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional
+    public void confirmPasswordReset(final String token, final String password) {
+        User user = userDAO.findByToken(token);
+        if (user == null) {
+            throw new NotFoundException("User with token " + token);
+        }
+        provisioningManager.confirmPasswordReset(user, token, password);
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    public UserTO deleteSelf() {
+        UserTO userTO = binder.getAuthenticatedUserTO();
+
+        return delete(userTO.getKey());
+    }
+
+    @PreAuthorize("hasRole('USER_DELETE')")
+    @Override
+    public UserTO delete(final Long key) {
+        List<Role> ownedRoles = roleDAO.findOwnedByUser(key);
+        if (!ownedRoles.isEmpty()) {
+            List<String> owned = new ArrayList<String>(ownedRoles.size());
+            for (Role role : ownedRoles) {
+                owned.add(role.getKey() + " " + role.getName());
+            }
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RoleOwnership);
+            sce.getElements().addAll(owned);
+            throw sce;
+        }
+
+        List<PropagationStatus> statuses = provisioningManager.delete(key);
+
+        final UserTO deletedTO;
+        User deleted = userDAO.find(key);
+        if (deleted == null) {
+            deletedTO = new UserTO();
+            deletedTO.setKey(key);
+        } else {
+            deletedTO = binder.getUserTO(key);
+        }
+        deletedTO.getPropagationStatusTOs().addAll(statuses);
+
+        return deletedTO;
+    }
+
+    @PreAuthorize("(hasRole('USER_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE) or "
+            + "(hasRole('USER_UPDATE') and "
+            + "(#bulkAction.operation == #bulkAction.operation.REACTIVATE or "
+            + "#bulkAction.operation == #bulkAction.operation.SUSPEND))")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        switch (bulkAction.getOperation()) {
+            case DELETE:
+                for (String key : bulkAction.getTargets()) {
+                    try {
+                        res.add(delete(Long.valueOf(key)).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing delete for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            case SUSPEND:
+                for (String key : bulkAction.getTargets()) {
+                    StatusMod statusMod = new StatusMod();
+                    statusMod.setId(Long.valueOf(key));
+                    statusMod.setType(StatusMod.ModType.SUSPEND);
+                    try {
+                        res.add(status(statusMod).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing suspend for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            case REACTIVATE:
+                for (String key : bulkAction.getTargets()) {
+                    StatusMod statusMod = new StatusMod();
+                    statusMod.setId(Long.valueOf(key));
+                    statusMod.setType(StatusMod.ModType.REACTIVATE);
+                    try {
+                        res.add(status(statusMod).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing reactivate for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        return res;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO unlink(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToRemove().addAll(resources);
+        Long updatedId = provisioningManager.unlink(userMod);
+
+        return binder.getUserTO(updatedId);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO link(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToAdd().addAll(resources);
+        return binder.getUserTO(provisioningManager.link(userMod));
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO unassign(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToRemove().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO assign(
+            final Long key,
+            final Collection<String> resources,
+            final boolean changepwd,
+            final String password) {
+
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToAdd().addAll(resources);
+
+        if (changepwd) {
+            StatusMod statusMod = new StatusMod();
+            statusMod.setOnSyncope(false);
+            statusMod.getResourceNames().addAll(resources);
+            userMod.setPwdPropRequest(statusMod);
+            userMod.setPassword(password);
+        }
+
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO deprovision(final Long key, final Collection<String> resources) {
+        final User user = userDAO.authFecthUser(key);
+
+        List<PropagationStatus> statuses = provisioningManager.deprovision(key, resources);
+
+        final UserTO updatedUserTO = binder.getUserTO(user);
+        updatedUserTO.getPropagationStatusTOs().addAll(statuses);
+        return updatedUserTO;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO provision(
+            final Long key,
+            final Collection<String> resources,
+            final boolean changePwd,
+            final String password) {
+
+        final UserTO original = binder.getUserTO(key);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(key, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
+    @Override
+    protected UserTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+        Object id = null;
+
+        if (!"confirmPasswordReset".equals(method.getName()) && ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof String) {
+                    id = (String) args[i];
+                } else if (args[i] instanceof UserTO) {
+                    id = ((UserTO) args[i]).getKey();
+                } else if (args[i] instanceof UserMod) {
+                    id = ((UserMod) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return id instanceof Long ? binder.getUserTO((Long) id) : binder.getUserTO((String) id);
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
new file mode 100644
index 0000000..08b5e71
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.to.WorkflowFormTO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class UserWorkflowLogic extends AbstractTransactionalLogic<WorkflowFormTO> {
+
+    @Autowired
+    private UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    private PropagationManager propagationManager;
+
+    @Autowired
+    private PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    private UserDataBinder binder;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_CLAIM')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public WorkflowFormTO claimForm(final String taskId) {
+        return uwfAdapter.claimForm(taskId);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    public UserTO executeWorkflowTask(final UserTO userTO, final String taskId) {
+        WorkflowResult<Long> updated = uwfAdapter.execute(userTO, taskId);
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                        new AbstractMap.SimpleEntry<UserMod, Boolean>(userMod, null),
+                        updated.getPropByRes(), updated.getPerformedTasks()));
+
+        taskExecutor.execute(tasks);
+
+        return binder.getUserTO(updated.getResult());
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public WorkflowFormTO getFormForUser(final Long key) {
+        User user = userDAO.authFecthUser(key);
+        return uwfAdapter.getForm(user.getWorkflowId());
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_LIST')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public List<WorkflowFormTO> getForms() {
+        return uwfAdapter.getForms();
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public List<WorkflowFormTO> getForms(final Long key, final String formName) {
+        User user = userDAO.authFecthUser(key);
+        return uwfAdapter.getForms(user.getWorkflowId(), formName);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_SUBMIT')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public UserTO submitForm(final WorkflowFormTO form) {
+        WorkflowResult<? extends AbstractAttributableMod> updated = uwfAdapter.submitForm(form);
+
+        // propByRes can be made empty by the workflow definition if no propagation should occur 
+        // (for example, with rejected users)
+        if (updated.getResult() instanceof UserMod
+                && updated.getPropByRes() != null && !updated.getPropByRes().isEmpty()) {
+
+            List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                    new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                            new AbstractMap.SimpleEntry<UserMod, Boolean>((UserMod) updated.getResult(), Boolean.TRUE),
+                            updated.getPropByRes(),
+                            updated.getPerformedTasks()));
+
+            taskExecutor.execute(tasks);
+        }
+
+        return binder.getUserTO(updated.getResult().getKey());
+    }
+
+    @Override
+    protected WorkflowFormTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
new file mode 100644
index 0000000..2e5ae74
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
@@ -0,0 +1,115 @@
+/*
+ * 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.server.logic;
+
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowDefinitionFormat;
+import org.apache.syncope.server.workflow.api.WorkflowException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class WorkflowLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+    @Autowired
+    private UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    private RoleWorkflowAdapter rwfAdapter;
+
+    private void exportDefinition(
+            final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final OutputStream os)
+            throws WorkflowException {
+
+        adapter.exportDefinition(format, os);
+    }
+
+    private WorkflowDefinitionFormat getFormat(final MediaType format) {
+        return format.equals(MediaType.APPLICATION_JSON_TYPE)
+                ? WorkflowDefinitionFormat.JSON
+                : WorkflowDefinitionFormat.XML;
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportUserDefinition(final MediaType format, final OutputStream os)
+            throws WorkflowException {
+
+        exportDefinition(uwfAdapter, getFormat(format), os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportRoleDefinition(final MediaType format, final OutputStream os)
+            throws WorkflowException {
+
+        exportDefinition(rwfAdapter, getFormat(format), os);
+    }
+
+    private void exportDiagram(final WorkflowAdapter adapter, final OutputStream os)
+            throws WorkflowException {
+        adapter.exportDiagram(os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportUserDiagram(final OutputStream os)
+            throws WorkflowException {
+
+        exportDiagram(uwfAdapter, os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportRoleDiagram(final OutputStream os)
+            throws WorkflowException {
+
+        exportDiagram(rwfAdapter, os);
+    }
+
+    private void importDefinition(
+            final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final String definition) {
+        adapter.importDefinition(format, definition);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+    public void importUserDefinition(final MediaType format, final String definition) {
+        importDefinition(uwfAdapter, getFormat(format), definition);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+    public void importRoleDefinition(final MediaType format, final String definition) {
+        importDefinition(rwfAdapter, getFormat(format), definition);
+    }
+
+    @Override
+    protected AbstractBaseBean resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
new file mode 100644
index 0000000..dae9fbf
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
@@ -0,0 +1,159 @@
+/*
+ * 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.server.logic.audit;
+
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.Properties;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.rmi.PortableRemoteObject;
+import javax.sql.DataSource;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.io.IOUtils;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSParser;
+
+/**
+ * LOG4J SQL connection factory that first attempts to obtain a {@link javax.sql.DataSource} from the JNDI name
+ * configured in Spring or, when not found, builds a new {@link javax.sql.DataSource DataSource} via Commons DBCP; if
+ * any datasource if found, the SQL init script is used to populate the database.
+ */
+public final class AuditConnectionFactory {
+
+    private static DataSource datasource;
+
+    private static final String PERSISTENCE_CONTEXT = "/persistenceContext.xml";
+
+    static {
+        // 1. Attempts to lookup for configured JNDI datasource (if present and available)
+        InputStream springConf = AuditConnectionFactory.class.getResourceAsStream(PERSISTENCE_CONTEXT);
+        String primary = null;
+        String fallback = null;
+        try {
+            DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
+            DOMImplementationLS impl = (DOMImplementationLS) reg.getDOMImplementation("LS");
+            LSParser parser = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
+            LSInput lsinput = impl.createLSInput();
+            lsinput.setByteStream(springConf);
+            Document source = parser.parse(lsinput);
+
+            XPathFactory xPathfactory = XPathFactory.newInstance();
+            XPath xpath = xPathfactory.newXPath();
+
+            XPathExpression expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+                    + "child::*[local-name()='property' and @name='primary']/@value");
+            primary = (String) expr.evaluate(source, XPathConstants.STRING);
+            expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+                    + "child::*[local-name()='property' and @name='fallback']/@value");
+            fallback = (String) expr.evaluate(source, XPathConstants.STRING);
+
+            expr = xpath.compile("//*[local-name()='property' and @name='jndiName']/@value");
+            String jndiName = (String) expr.evaluate(source, XPathConstants.STRING);
+
+            Context ctx = new InitialContext();
+            Object obj = ctx.lookup(jndiName);
+
+            datasource = (DataSource) PortableRemoteObject.narrow(obj, DataSource.class);
+        } catch (Exception e) {
+            // ignore
+        } finally {
+            IOUtils.closeQuietly(springConf);
+        }
+
+        // 2. Creates Commons DBCP datasource
+        String initSQLScript = null;
+        try {
+            Resource persistenceProperties = null;
+            if (primary != null) {
+                if (primary.startsWith("file:")) {
+                    persistenceProperties = new FileSystemResource(primary.substring(5));
+                }
+                if (primary.startsWith("classpath:")) {
+                    persistenceProperties = new ClassPathResource(primary.substring(10));
+                }
+            }
+            if ((persistenceProperties == null || !persistenceProperties.exists()) && fallback != null) {
+                if (fallback.startsWith("file:")) {
+                    persistenceProperties = new FileSystemResource(fallback.substring(5));
+                }
+                if (fallback.startsWith("classpath:")) {
+                    persistenceProperties = new ClassPathResource(fallback.substring(10));
+                }
+            }
+            Properties persistence = PropertiesLoaderUtils.loadProperties(persistenceProperties);
+
+            initSQLScript = persistence.getProperty("audit.sql");
+
+            if (datasource == null) {
+                BasicDataSource bds = new BasicDataSource();
+                bds.setDriverClassName(persistence.getProperty("jpa.driverClassName"));
+                bds.setUrl(persistence.getProperty("jpa.url"));
+                bds.setUsername(persistence.getProperty("jpa.username"));
+                bds.setPassword(persistence.getProperty("jpa.password"));
+
+                bds.setLogAbandoned(true);
+                bds.setRemoveAbandonedOnBorrow(true);
+                bds.setRemoveAbandonedOnMaintenance(true);
+
+                datasource = bds;
+            }
+        } catch (Exception e) {
+            throw new IllegalStateException("Audit datasource configuration failed", e);
+        }
+
+        // 3. Initializes the chosen datasource
+        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
+        populator.setScripts(new Resource[] { new ClassPathResource("/audit/" + initSQLScript) });
+        // forces no statement separation
+        populator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
+        Connection conn = DataSourceUtils.getConnection(datasource);
+        try {
+            populator.populate(conn);
+        } finally {
+            DataSourceUtils.releaseConnection(conn, datasource);
+        }
+    }
+
+    public static Connection getConnection() {
+        if (datasource != null) {
+            return DataSourceUtils.getConnection(datasource);
+        }
+
+        throw new IllegalStateException("Audit dataSource init failed: check logs");
+    }
+
+    private AuditConnectionFactory() {
+        // empty constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
new file mode 100644
index 0000000..ed674fd
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
@@ -0,0 +1,107 @@
+/*
+ * 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.server.logic.audit;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.persistence.api.dao.LoggerDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class AuditManager {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(AuditManager.class);
+
+    @Autowired
+    private LoggerDAO loggerDAO;
+
+    public void audit(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        final Throwable throwable;
+        final StringBuilder message = new StringBuilder();
+
+        message.append("BEFORE:\n");
+        message.append("\t").append(before == null ? "unknown" : before).append("\n");
+
+        message.append("INPUT:\n");
+
+        if (ArrayUtils.isNotEmpty(input)) {
+            for (Object obj : input) {
+                message.append("\t").append(obj == null ? null : obj.toString()).append("\n");
+            }
+        } else {
+            message.append("\t").append("none").append("\n");
+        }
+
+        message.append("OUTPUT:\n");
+
+        if (output instanceof Throwable) {
+            throwable = (Throwable) output;
+            message.append("\t").append(throwable.getMessage());
+        } else {
+            throwable = null;
+            message.append("\t").append(output == null ? "none" : output.toString());
+        }
+
+        AuditLoggerName auditLoggerName = null;
+        try {
+            auditLoggerName = new AuditLoggerName(type, category, subcategory, event, result);
+        } catch (IllegalArgumentException e) {
+            LOG.error("Invalid audit parameters, aborting...", e);
+        }
+
+        if (auditLoggerName != null) {
+            org.apache.syncope.persistence.api.entity.Logger syncopeLogger =
+                     loggerDAO.find(auditLoggerName.toLoggerName());
+            if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
+                StringBuilder auditMessage = new StringBuilder();
+
+                final SecurityContext ctx = SecurityContextHolder.getContext();
+                if (ctx != null && ctx.getAuthentication() != null) {
+                    auditMessage.append('[').append(ctx.getAuthentication().getName()).append(']').append(' ');
+                }
+                auditMessage.append(message);
+
+                final Logger logger = LoggerFactory.getLogger(auditLoggerName.toLoggerName());
+                if (throwable == null) {
+                    logger.debug(auditMessage.toString());
+                } else {
+                    logger.debug(auditMessage.toString(), throwable);
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
new file mode 100644
index 0000000..d84c62f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
@@ -0,0 +1,918 @@
+/*
+ * 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.server.logic.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.persistence.api.dao.DerAttrDAO;
+import org.apache.syncope.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.MembershipDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Schema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+import org.apache.syncope.server.utils.MappingUtil;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractAttributableDataBinder {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractAttributableDataBinder.class);
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    protected DerSchemaDAO derSchemaDAO;
+
+    @Autowired
+    protected VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    protected PlainAttrDAO plainAttrDAO;
+
+    @Autowired
+    protected DerAttrDAO derAttrDAO;
+
+    @Autowired
+    protected VirAttrDAO virAttrDAO;
+
+    @Autowired
+    protected PlainAttrValueDAO plainAttrValueDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    protected MembershipDAO membershipDAO;
+
+    @Autowired
+    protected PolicyDAO policyDAO;
+
+    @Autowired
+    protected EntityFactory entityFactory;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    @SuppressWarnings("unchecked")
+    protected <T extends Schema> T getSchema(final String schemaName, final Class<T> reference) {
+        T result = null;
+
+        if (PlainSchema.class.isAssignableFrom(reference)) {
+            result = (T) getPlainSchema(schemaName, (Class<? extends PlainSchema>) reference);
+        } else if (DerSchema.class.isAssignableFrom(reference)) {
+            result = (T) getDerSchema(schemaName, (Class<? extends DerSchema>) reference);
+        } else if (VirSchema.class.isAssignableFrom(reference)) {
+            result = (T) getVirSchema(schemaName, (Class<? extends VirSchema>) reference);
+        }
+
+        return result;
+    }
+
+    protected <T extends PlainSchema> T getPlainSchema(final String schemaName, final Class<T> reference) {
+        T schema = null;
+        if (StringUtils.isNotBlank(schemaName)) {
+            schema = plainSchemaDAO.find(schemaName, reference);
+
+            // safely ignore invalid schemas from AttrTO
+            // see http://code.google.com/p/syncope/issues/detail?id=17
+            if (schema == null) {
+                LOG.debug("Ignoring invalid schema {}", schemaName);
+            } else if (schema.isReadonly()) {
+                schema = null;
+
+                LOG.debug("Ignoring readonly schema {}", schemaName);
+            }
+        }
+
+        return schema;
+    }
+
+    private <T extends DerSchema> T getDerSchema(final String derSchemaName, final Class<T> reference) {
+        T derivedSchema = null;
+        if (StringUtils.isNotBlank(derSchemaName)) {
+            derivedSchema = derSchemaDAO.find(derSchemaName, reference);
+            if (derivedSchema == null) {
+                LOG.debug("Ignoring invalid derived schema {}", derSchemaName);
+            }
+        }
+
+        return derivedSchema;
+    }
+
+    private <T extends VirSchema> T getVirSchema(final String virSchemaName, final Class<T> reference) {
+        T virtualSchema = null;
+        if (StringUtils.isNotBlank(virSchemaName)) {
+            virtualSchema = virSchemaDAO.find(virSchemaName, reference);
+
+            if (virtualSchema == null) {
+                LOG.debug("Ignoring invalid virtual schema {}", virSchemaName);
+            }
+        }
+
+        return virtualSchema;
+    }
+
+    protected void fillAttribute(final List<String> values, final AttributableUtil attributableUtil,
+            final PlainSchema schema, final PlainAttr attr, final SyncopeClientException invalidValues) {
+
+        // if schema is multivalue, all values are considered for addition;
+        // otherwise only the fist one - if provided - is considered
+        List<String> valuesProvided = schema.isMultivalue()
+                ? values
+                : (values.isEmpty()
+                        ? Collections.<String>emptyList()
+                        : Collections.singletonList(values.iterator().next()));
+
+        for (String value : valuesProvided) {
+            if (value == null || value.isEmpty()) {
+                LOG.debug("Null value for {}, ignoring", schema.getKey());
+            } else {
+                try {
+                    attr.addValue(value, attributableUtil);
+                } catch (InvalidPlainAttrValueException e) {
+                    LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);
+
+                    invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil, final ExternalResource resource,
+            final Attributable attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+        boolean result = false;
+
+        final List<MappingItem> mappings = MappingUtil.getMatchingMappingItems(
+                attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION), intAttrName, intMappingType);
+        for (Iterator<MappingItem> itor = mappings.iterator(); itor.hasNext() && !result;) {
+            final MappingItem mapping = itor.next();
+            result |= JexlUtil.evaluateMandatoryCondition(mapping.getMandatoryCondition(), attributable);
+        }
+
+        return result;
+    }
+
+    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil,
+            final Attributable<?, ?, ?> attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+        boolean result = false;
+
+        if (attributable instanceof Subject) {
+            for (Iterator<? extends ExternalResource> itor = ((Subject<?, ?, ?>) attributable).getResources().iterator();
+                    itor.hasNext() && !result;) {
+
+                final ExternalResource resource = itor.next();
+                if (resource.isEnforceMandatoryCondition()) {
+                    result |= evaluateMandatoryCondition(attrUtil, resource, attributable, intAttrName, intMappingType);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private SyncopeClientException checkMandatory(final AttributableUtil attrUtil,
+            final Attributable<?, ?, ?> attributable) {
+
+        SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+
+        // Check if there is some mandatory schema defined for which no value has been provided
+        List<? extends PlainSchema> plainSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                plainSchemas = ((Role) attributable).getAttrTemplateSchemas(RPlainAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                plainSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MPlainAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                plainSchemas = plainSchemaDAO.findAll(attrUtil.plainSchemaClass());
+        }
+        for (PlainSchema schema : plainSchemas) {
+            if (attributable.getPlainAttr(schema.getKey()) == null
+                    && !schema.isReadonly()
+                    && (JexlUtil.evaluateMandatoryCondition(schema.getMandatoryCondition(), attributable)
+                    || evaluateMandatoryCondition(attrUtil, attributable, schema.getKey(),
+                            attrUtil.intMappingType()))) {
+
+                LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");
+
+                reqValMissing.getElements().add(schema.getKey());
+            }
+        }
+
+        List<? extends DerSchema> derSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                derSchemas = ((Role) attributable).getAttrTemplateSchemas(RDerAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                derSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MDerAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
+        }
+        for (DerSchema derSchema : derSchemas) {
+            if (attributable.getDerAttr(derSchema.getKey()) == null
+                    && evaluateMandatoryCondition(attrUtil, attributable, derSchema.getKey(),
+                            attrUtil.derIntMappingType())) {
+
+                LOG.error("Mandatory derived schema " + derSchema.getKey() + " does not evaluate to any value");
+
+                reqValMissing.getElements().add(derSchema.getKey());
+            }
+        }
+
+        List<? extends VirSchema> virSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                virSchemas = ((Role) attributable).getAttrTemplateSchemas(RVirAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                virSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MVirAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
+        }
+        for (VirSchema virSchema : virSchemas) {
+            if (attributable.getVirAttr(virSchema.getKey()) == null
+                    && !virSchema.isReadonly()
+                    && evaluateMandatoryCondition(attrUtil, attributable, virSchema.getKey(),
+                            attrUtil.virIntMappingType())) {
+
+                LOG.error("Mandatory virtual schema " + virSchema.getKey() + " not provided with values");
+
+                reqValMissing.getElements().add(virSchema.getKey());
+            }
+        }
+
+        return reqValMissing;
+    }
+
+    private void setPlainAttrSchema(final Attributable<?, ?, ?> attributable,
+            final PlainAttr attr, final PlainSchema schema) {
+
+        if (attr instanceof UPlainAttr) {
+            ((UPlainAttr) attr).setSchema((UPlainSchema) schema);
+        } else if (attr instanceof RPlainAttr) {
+            RPlainAttrTemplate template =
+                    ((Role) attributable).getAttrTemplate(RPlainAttrTemplate.class, schema.getKey());
+            if (template != null) {
+                ((RPlainAttr) attr).setTemplate(template);
+            }
+        } else if (attr instanceof MPlainAttr) {
+            MPlainAttrTemplate template = ((Membership) attributable).getRole().
+                    getAttrTemplate(MPlainAttrTemplate.class, schema.getKey());
+            if (template != null) {
+                ((MPlainAttr) attr).setTemplate(template);
+            }
+        }
+    }
+
+    private void setDerAttrSchema(final Attributable<?, ?, ?> attributable,
+            final DerAttr derAttr, final DerSchema derSchema) {
+
+        if (derAttr instanceof UDerAttr) {
+            ((UDerAttr) derAttr).setSchema((UDerSchema) derSchema);
+        } else if (derAttr instanceof RDerAttr) {
+            RDerAttrTemplate template = ((Role) attributable).
+                    getAttrTemplate(RDerAttrTemplate.class, derSchema.getKey());
+            if (template != null) {
+                ((RDerAttr) derAttr).setTemplate(template);
+            }
+        } else if (derAttr instanceof MDerAttr) {
+            MDerAttrTemplate template = ((Membership) attributable).getRole().
+                    getAttrTemplate(MDerAttrTemplate.class, derSchema.getKey());
+            if (template != null) {
+                ((MDerAttr) derAttr).setTemplate(template);
+            }
+        }
+    }
+
+    private void setVirAttrSchema(final Attributable<?, ?, ?> attributable,
+            final VirAttr virAttr, final VirSchema virSchema) {
+
+        if (virAttr instanceof UVirAttr) {
+            ((UVirAttr) virAttr).setSchema((UVirSchema) virSchema);
+        } else if (virAttr instanceof RVirAttr) {
+            RVirAttrTemplate template = ((Role) attributable).
+                    getAttrTemplate(RVirAttrTemplate.class, virSchema.getKey());
+            if (template != null) {
+                ((RVirAttr) virAttr).setTemplate(template);
+            }
+        } else if (virAttr instanceof MVirAttr) {
+            MVirAttrTemplate template =
+                    ((Membership) attributable).getRole().
+                    getAttrTemplate(MVirAttrTemplate.class, virSchema.getKey());
+            if (template != null) {
+                ((MVirAttr) virAttr).setTemplate(template);
+            }
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public PropagationByResource fillVirtual(final Attributable attributable,
+            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final AttributableUtil attrUtil) {
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        final Set<ExternalResource> externalResources = new HashSet<>();
+        if (attributable instanceof Subject) {
+            externalResources.addAll(((Subject<?, ?, ?>) attributable).getResources());
+        }
+
+        if (attributable instanceof Membership) {
+            externalResources.clear();
+            externalResources.addAll(((Membership) attributable).getUser().getResources());
+        }
+
+        // 1. virtual attributes to be removed
+        for (String vAttrToBeRemoved : vAttrsToBeRemoved) {
+            VirSchema virSchema = getVirSchema(vAttrToBeRemoved, attrUtil.virSchemaClass());
+            if (virSchema != null) {
+                VirAttr virAttr = attributable.getVirAttr(virSchema.getKey());
+                if (virAttr == null) {
+                    LOG.debug("No virtual attribute found for schema {}", virSchema.getKey());
+                } else {
+                    attributable.removeVirAttr(virAttr);
+                    virAttrDAO.delete(virAttr);
+                }
+
+                for (ExternalResource resource : resourceDAO.findAll()) {
+                    for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                        if (virSchema.getKey().equals(mapItem.getIntAttrName())
+                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+                                && externalResources.contains(resource)) {
+
+                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                            // Using virtual attribute as AccountId must be avoided
+                            if (mapItem.isAccountid() && virAttr != null && !virAttr.getValues().isEmpty()) {
+                                propByRes.addOldAccountId(resource.getKey(), virAttr.getValues().get(0));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Virtual attributes to be removed:\n{}", propByRes);
+
+        // 2. virtual attributes to be updated
+        for (AttrMod vAttrToBeUpdated : vAttrsToBeUpdated) {
+            VirSchema virSchema = getVirSchema(vAttrToBeUpdated.getSchema(), attrUtil.virSchemaClass());
+            VirAttr virAttr = null;
+            if (virSchema != null) {
+                virAttr = attributable.getVirAttr(virSchema.getKey());
+                if (virAttr == null) {
+                    virAttr = attrUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", vAttrToBeUpdated);
+                    } else {
+                        attributable.addVirAttr(virAttr);
+                    }
+                }
+            }
+
+            if (virSchema != null && virAttr != null && virAttr.getSchema() != null) {
+                for (ExternalResource resource : resourceDAO.findAll()) {
+                    for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                        if (virSchema.getKey().equals(mapItem.getIntAttrName())
+                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+                                && externalResources.contains(resource)) {
+
+                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                        }
+                    }
+                }
+
+                final List<String> values = new ArrayList<>(virAttr.getValues());
+                values.removeAll(vAttrToBeUpdated.getValuesToBeRemoved());
+                values.addAll(vAttrToBeUpdated.getValuesToBeAdded());
+
+                virAttr.getValues().clear();
+                virAttr.getValues().addAll(values);
+
+                // Owner cannot be specified before otherwise a virtual attribute remove will be invalidated.
+                virAttr.setOwner(attributable);
+            }
+        }
+
+        LOG.debug("Virtual attributes to be added:\n{}", propByRes);
+
+        return propByRes;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected PropagationByResource fill(final Attributable attributable,
+            final AbstractAttributableMod attributableMod, final AttributableUtil attrUtil,
+            final SyncopeClientCompositeException scce) {
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+        if (attributable instanceof Subject && attributableMod instanceof AbstractSubjectMod) {
+            // 1. resources to be removed
+            for (String resourceToBeRemoved : ((AbstractSubjectMod) attributableMod).getResourcesToRemove()) {
+                ExternalResource resource = resourceDAO.find(resourceToBeRemoved);
+                if (resource != null) {
+                    propByRes.add(ResourceOperation.DELETE, resource.getKey());
+                    ((Subject<?, ?, ?>) attributable).removeResource(resource);
+                }
+            }
+
+            LOG.debug("Resources to be removed:\n{}", propByRes);
+
+            // 2. resources to be added
+            for (String resourceToBeAdded : ((AbstractSubjectMod) attributableMod).getResourcesToAdd()) {
+                ExternalResource resource = resourceDAO.find(resourceToBeAdded);
+                if (resource != null) {
+                    propByRes.add(ResourceOperation.CREATE, resource.getKey());
+                    ((Subject<?, ?, ?>) attributable).addResource(resource);
+                }
+            }
+
+            LOG.debug("Resources to be added:\n{}", propByRes);
+        }
+
+        // 3. attributes to be removed
+        for (String attributeToBeRemoved : attributableMod.getAttrsToRemove()) {
+            PlainSchema schema = getPlainSchema(attributeToBeRemoved, attrUtil.plainSchemaClass());
+            if (schema != null) {
+                PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+                if (attr == null) {
+                    LOG.debug("No attribute found for schema {}", schema);
+                } else {
+                    String newValue = null;
+                    for (AttrMod mod : attributableMod.getAttrsToUpdate()) {
+                        if (schema.getKey().equals(mod.getSchema())) {
+                            newValue = mod.getValuesToBeAdded().get(0);
+                        }
+                    }
+
+                    if (!schema.isUniqueConstraint()
+                            || (!attr.getUniqueValue().getStringValue().equals(newValue))) {
+
+                        attributable.removePlainAttr(attr);
+                        plainAttrDAO.delete(attr.getKey(), attrUtil.plainAttrClass());
+                    }
+                }
+
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (schema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                                if (mapItem.isAccountid() && attr != null
+                                        && !attr.getValuesAsStrings().isEmpty()) {
+
+                                    propByRes.addOldAccountId(resource.getKey(),
+                                            attr.getValuesAsStrings().iterator().next());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Attributes to be removed:\n{}", propByRes);
+
+        // 4. attributes to be updated
+        for (AttrMod attributeMod : attributableMod.getAttrsToUpdate()) {
+            PlainSchema schema = getPlainSchema(attributeMod.getSchema(), attrUtil.plainSchemaClass());
+            PlainAttr attr = null;
+            if (schema != null) {
+                attr = attributable.getPlainAttr(schema.getKey());
+                if (attr == null) {
+                    attr = attrUtil.newPlainAttr();
+                    setPlainAttrSchema(attributable, attr, schema);
+                    if (attr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeMod);
+                    } else {
+                        attr.setOwner(attributable);
+                        attributable.addPlainAttr(attr);
+                    }
+                }
+            }
+
+            if (schema != null && attr != null && attr.getSchema() != null) {
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (schema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                            }
+                        }
+                    }
+                }
+
+                // 1.1 remove values
+                Set<Long> valuesToBeRemoved = new HashSet<>();
+                for (String valueToBeRemoved : attributeMod.getValuesToBeRemoved()) {
+                    if (attr.getSchema().isUniqueConstraint()) {
+                        if (attr.getUniqueValue() != null
+                                && valueToBeRemoved.equals(attr.getUniqueValue().getValueAsString())) {
+
+                            valuesToBeRemoved.add(attr.getUniqueValue().getKey());
+                        }
+                    } else {
+                        for (PlainAttrValue mav : attr.getValues()) {
+                            if (valueToBeRemoved.equals(mav.getValueAsString())) {
+                                valuesToBeRemoved.add(mav.getKey());
+                            }
+                        }
+                    }
+                }
+                for (Long attributeValueId : valuesToBeRemoved) {
+                    plainAttrValueDAO.delete(attributeValueId, attrUtil.plainAttrValueClass());
+                }
+
+                // 1.2 add values
+                List<String> valuesToBeAdded = attributeMod.getValuesToBeAdded();
+                if (valuesToBeAdded != null && !valuesToBeAdded.isEmpty()
+                        && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
+                        || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {
+
+                    fillAttribute(attributeMod.getValuesToBeAdded(), attrUtil, schema, attr, invalidValues);
+                }
+
+                // if no values are in, the attribute can be safely removed
+                if (attr.getValuesAsStrings().isEmpty()) {
+                    plainAttrDAO.delete(attr);
+                }
+            }
+        }
+
+        if (!invalidValues.isEmpty()) {
+            scce.addException(invalidValues);
+        }
+
+        LOG.debug("Attributes to be updated:\n{}", propByRes);
+
+        // 5. derived attributes to be removed
+        for (String derAttrToBeRemoved : attributableMod.getDerAttrsToRemove()) {
+            DerSchema derSchema = getDerSchema(derAttrToBeRemoved, attrUtil.derSchemaClass());
+            if (derSchema != null) {
+                DerAttr derAttr = attributable.getDerAttr(derSchema.getKey());
+                if (derAttr == null) {
+                    LOG.debug("No derived attribute found for schema {}", derSchema.getKey());
+                } else {
+                    derAttrDAO.delete(derAttr);
+                }
+
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (derSchema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                                if (mapItem.isAccountid() && derAttr != null
+                                        && !derAttr.getValue(attributable.getPlainAttrs()).isEmpty()) {
+
+                                    propByRes.addOldAccountId(resource.getKey(),
+                                            derAttr.getValue(attributable.getPlainAttrs()));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Derived attributes to be removed:\n{}", propByRes);
+
+        // 6. derived attributes to be added
+        for (String derAttrToBeAdded : attributableMod.getDerAttrsToAdd()) {
+            DerSchema derSchema = getDerSchema(derAttrToBeAdded, attrUtil.derSchemaClass());
+            if (derSchema != null) {
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (derSchema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                            }
+                        }
+                    }
+                }
+
+                DerAttr derAttr = attrUtil.newDerAttr();
+                setDerAttrSchema(attributable, derAttr, derSchema);
+                if (derAttr.getSchema() == null) {
+                    LOG.debug("Ignoring {} because no valid schema or template was found", derAttrToBeAdded);
+                } else {
+                    derAttr.setOwner(attributable);
+                    attributable.addDerAttr(derAttr);
+                }
+            }
+        }
+
+        LOG.debug("Derived attributes to be added:\n{}", propByRes);
+
+        // 7. virtual attributes: for users and roles this is delegated to PropagationManager
+        if (AttributableType.USER != attrUtil.getType() && AttributableType.ROLE != attrUtil.getType()) {
+            fillVirtual(attributable, attributableMod.getVirAttrsToRemove(),
+                    attributableMod.getVirAttrsToUpdate(), attrUtil);
+        }
+
+        // Finally, check if mandatory values are missing
+        SyncopeClientException requiredValuesMissing = checkMandatory(attrUtil, attributable);
+        if (!requiredValuesMissing.isEmpty()) {
+            scce.addException(requiredValuesMissing);
+        }
+
+        // Throw composite exception if there is at least one element set in the composing exceptions
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+
+        return propByRes;
+    }
+
+    /**
+     * Add virtual attributes and specify values to be propagated.
+     *
+     * @param attributable attributable.
+     * @param vAttrs virtual attributes to be added.
+     * @param attrUtil attributable util.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void fillVirtual(final Attributable attributable, final Collection<AttrTO> vAttrs,
+            final AttributableUtil attrUtil) {
+
+        for (AttrTO attributeTO : vAttrs) {
+            VirAttr virAttr = attributable.getVirAttr(attributeTO.getSchema());
+            if (virAttr == null) {
+                VirSchema virSchema = getVirSchema(attributeTO.getSchema(), attrUtil.virSchemaClass());
+                if (virSchema != null) {
+                    virAttr = attrUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                    } else {
+                        virAttr.setOwner(attributable);
+                        attributable.addVirAttr(virAttr);
+                        virAttr.getValues().clear();
+                        virAttr.getValues().addAll(attributeTO.getValues());
+                    }
+                }
+            } else {
+                virAttr.getValues().clear();
+                virAttr.getValues().addAll(attributeTO.getValues());
+            }
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void fill(final Attributable attributable, final AbstractAttributableTO attributableTO,
+            final AttributableUtil attributableUtil, final SyncopeClientCompositeException scce) {
+
+        // 1. attributes
+        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+        // Only consider attributeTO with values
+        for (AttrTO attributeTO : attributableTO.getPlainAttrs()) {
+            if (attributeTO.getValues() != null && !attributeTO.getValues().isEmpty()) {
+                PlainSchema schema = getPlainSchema(attributeTO.getSchema(), attributableUtil.plainSchemaClass());
+
+                if (schema != null) {
+                    PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+                    if (attr == null) {
+                        attr = attributableUtil.newPlainAttr();
+                        setPlainAttrSchema(attributable, attr, schema);
+                    }
+                    if (attr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                    } else {
+                        fillAttribute(attributeTO.getValues(), attributableUtil, schema, attr, invalidValues);
+
+                        if (!attr.getValuesAsStrings().isEmpty()) {
+                            attributable.addPlainAttr(attr);
+                            attr.setOwner(attributable);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!invalidValues.isEmpty()) {
+            scce.addException(invalidValues);
+        }
+
+        // 2. derived attributes
+        for (AttrTO attributeTO : attributableTO.getDerAttrs()) {
+            DerSchema derSchema = getDerSchema(attributeTO.getSchema(), attributableUtil.derSchemaClass());
+
+            if (derSchema != null) {
+                DerAttr derAttr = attributableUtil.newDerAttr();
+                setDerAttrSchema(attributable, derAttr, derSchema);
+                if (derAttr.getSchema() == null) {
+                    LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                } else {
+                    derAttr.setOwner(attributable);
+                    attributable.addDerAttr(derAttr);
+                }
+            }
+        }
+
+        // 3. user and role virtual attributes will be evaluated by the propagation manager only (if needed).
+        if (AttributableType.USER == attributableUtil.getType()
+                || AttributableType.ROLE == attributableUtil.getType()) {
+
+            for (AttrTO vattrTO : attributableTO.getVirAttrs()) {
+                VirSchema virSchema = getVirSchema(vattrTO.getSchema(), attributableUtil.virSchemaClass());
+
+                if (virSchema != null) {
+                    VirAttr virAttr = attributableUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", vattrTO);
+                    } else {
+                        virAttr.setOwner(attributable);
+                        attributable.addVirAttr(virAttr);
+                    }
+                }
+            }
+        }
+
+        fillVirtual(attributable, attributableTO.getVirAttrs(), attributableUtil);
+
+        // 4. resources
+        if (attributable instanceof Subject && attributableTO instanceof AbstractSubjectTO) {
+            for (String resourceName : ((AbstractSubjectTO) attributableTO).getResources()) {
+                ExternalResource resource = resourceDAO.find(resourceName);
+
+                if (resource != null) {
+                    ((Subject<?, ?, ?>) attributable).addResource(resource);
+                }
+            }
+        }
+
+        SyncopeClientException requiredValuesMissing = checkMandatory(attributableUtil, attributable);
+        if (!requiredValuesMissing.isEmpty()) {
+            scce.addException(requiredValuesMissing);
+        }
+
+        // Throw composite exception if there is at least one element set in the composing exceptions
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+    }
+
+    protected void fillTO(final AbstractAttributableTO attributableTO,
+            final Collection<? extends PlainAttr> attrs,
+            final Collection<? extends DerAttr> derAttrs,
+            final Collection<? extends VirAttr> virAttrs,
+            final Collection<? extends ExternalResource> resources) {
+
+        AttrTO attributeTO;
+        for (PlainAttr attr : attrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(attr.getSchema().getKey());
+            attributeTO.getValues().addAll(attr.getValuesAsStrings());
+            attributeTO.setReadonly(attr.getSchema().isReadonly());
+
+            attributableTO.getPlainAttrs().add(attributeTO);
+        }
+
+        for (DerAttr derAttr : derAttrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(derAttr.getSchema().getKey());
+            attributeTO.getValues().add(derAttr.getValue(attrs));
+            attributeTO.setReadonly(true);
+
+            attributableTO.getDerAttrs().add(attributeTO);
+        }
+
+        for (VirAttr virAttr : virAttrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(virAttr.getSchema().getKey());
+            attributeTO.getValues().addAll(virAttr.getValues());
+            attributeTO.setReadonly(virAttr.getSchema().isReadonly());
+
+            attributableTO.getVirAttrs().add(attributeTO);
+        }
+
+        if (attributableTO instanceof AbstractSubjectTO) {
+            for (ExternalResource resource : resources) {
+                ((AbstractSubjectTO) attributableTO).getResources().add(resource.getKey());
+            }
+        }
+    }
+}