syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [26/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in
Date Mon, 12 Jan 2015 16:32:05 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPASubjectSearchDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPASubjectSearchDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPASubjectSearchDAO.java
new file mode 100644
index 0000000..f4475f8
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPASubjectSearchDAO.java
@@ -0,0 +1,718 @@
+/*
+ * 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.persistence.jpa.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import javax.persistence.Entity;
+import javax.persistence.Query;
+import javax.persistence.TemporalType;
+import javax.validation.ValidationException;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.server.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.server.persistence.api.dao.search.EntitlementCond;
+import org.apache.syncope.server.persistence.api.dao.search.MembershipCond;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.server.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.server.persistence.api.dao.search.SubjectCond;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.ReflectionUtils;
+
+@Repository
+public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> implements SubjectSearchDAO {
+
+    private static final String EMPTY_ATTR_QUERY = "SELECT subject_id FROM user_search_attr WHERE 1=2";
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private PlainSchemaDAO schemaDAO;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    private String getAdminRolesFilter(final Set<Long> adminRoles, final SubjectType type) {
+        final StringBuilder adminRolesFilter = new StringBuilder();
+
+        if (type == SubjectType.USER) {
+            adminRolesFilter.append("SELECT user_id AS subject_id FROM Membership M1 WHERE role_id IN (").
+                    append("SELECT role_id FROM Membership M2 WHERE M2.user_id=M1.user_id ").
+                    append("AND role_id NOT IN (");
+        }
+
+        adminRolesFilter.append("SELECT id AS ").
+                append(type == SubjectType.USER ? "role" : "subject").
+                append("_id FROM SyncopeRole");
+
+        boolean firstRole = true;
+
+        for (Long adminRoleId : adminRoles) {
+            if (firstRole) {
+                adminRolesFilter.append(" WHERE");
+                firstRole = false;
+            } else {
+                adminRolesFilter.append(type == SubjectType.USER ? " OR" : " AND");
+            }
+            adminRolesFilter.append(type == SubjectType.USER ? " id = " : " id <> ").append(adminRoleId);
+        }
+
+        if (type == SubjectType.USER) {
+            adminRolesFilter.append("))");
+        }
+
+        return adminRolesFilter.toString();
+    }
+
+    @Override
+    public int count(final Set<Long> adminRoles, final SearchCond searchCondition, final SubjectType type) {
+        List<Object> parameters = Collections.synchronizedList(new ArrayList<>());
+
+        // 1. get the query string from the search condition
+        SearchSupport svs = new SearchSupport(type);
+        StringBuilder queryString = getQuery(searchCondition, parameters, type, svs);
+
+        // 2. take into account administrative roles
+        queryString.insert(0, "SELECT u.subject_id FROM (");
+        queryString.append(") u WHERE subject_id NOT IN (");
+        queryString.append(getAdminRolesFilter(adminRoles, type)).append(')');
+
+        // 3. prepare the COUNT query
+        queryString.insert(0, "SELECT COUNT(subject_id) FROM (");
+        queryString.append(") count_subject_id");
+
+        Query countQuery = entityManager.createNativeQuery(queryString.toString());
+        fillWithParameters(countQuery, parameters);
+
+        LOG.debug("Native count query\n{}\nwith parameters\n{}", queryString.toString(), parameters);
+
+        int result = ((Number) countQuery.getSingleResult()).intValue();
+        LOG.debug("Native count query result: {}", result);
+
+        return result;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> List<T> search(
+            final Set<Long> adminRoles, final SearchCond searchCondition, final SubjectType type) {
+
+        return search(adminRoles, searchCondition, Collections.<OrderByClause>emptyList(), type);
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> List<T> search(
+            final Set<Long> adminRoles, final SearchCond searchCondition, final List<OrderByClause> orderBy,
+            final SubjectType type) {
+
+        return search(adminRoles, searchCondition, -1, -1, orderBy, type);
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> List<T> search(
+            final Set<Long> adminRoles, final SearchCond searchCondition, final int page, final int itemsPerPage,
+            final List<OrderByClause> orderBy, final SubjectType type) {
+
+        List<T> result = Collections.<T>emptyList();
+
+        if (adminRoles != null && (!adminRoles.isEmpty() || roleDAO.findAll().isEmpty())) {
+            LOG.debug("Search condition:\n{}", searchCondition);
+
+            if (searchCondition != null && searchCondition.isValid()) {
+                try {
+                    result = doSearch(adminRoles, searchCondition, page, itemsPerPage, orderBy, type);
+                } catch (Exception e) {
+                    LOG.error("While searching for {}", type, e);
+                }
+            } else {
+                LOG.error("Invalid search condition:\n{}", searchCondition);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> boolean matches(
+            final T subject, final SearchCond searchCondition, final SubjectType type) {
+
+        List<Object> parameters = Collections.synchronizedList(new ArrayList<>());
+
+        // 1. get the query string from the search condition
+        SearchSupport svs = new SearchSupport(type);
+        StringBuilder queryString = getQuery(searchCondition, parameters, type, svs);
+
+        boolean matches;
+        if (queryString.length() == 0) {
+            // Could be empty: got into a role search with a single membership condition ...
+            matches = false;
+        } else {
+            // 2. take into account the passed user
+            queryString.insert(0, "SELECT u.subject_id FROM (");
+            queryString.append(") u WHERE subject_id=?").append(setParameter(parameters, subject.getKey()));
+
+            // 3. prepare the search query
+            Query query = entityManager.createNativeQuery(queryString.toString());
+
+            // 4. populate the search query with parameter values
+            fillWithParameters(query, parameters);
+
+            // 5. executes query
+            matches = !query.getResultList().isEmpty();
+        }
+
+        return matches;
+    }
+
+    private int setParameter(final List<Object> parameters, final Object parameter) {
+        int key;
+        synchronized (parameters) {
+            parameters.add(parameter);
+            key = parameters.size();
+        }
+
+        return key;
+    }
+
+    private void fillWithParameters(final Query query, final List<Object> parameters) {
+        for (int i = 0; i < parameters.size(); i++) {
+            if (parameters.get(i) instanceof Date) {
+                query.setParameter(i + 1, (Date) parameters.get(i), TemporalType.TIMESTAMP);
+            } else if (parameters.get(i) instanceof Boolean) {
+                query.setParameter(i + 1, ((Boolean) parameters.get(i))
+                        ? 1
+                        : 0);
+            } else {
+                query.setParameter(i + 1, parameters.get(i));
+            }
+        }
+    }
+
+    private StringBuilder buildSelect(final OrderBySupport orderBySupport) {
+        final StringBuilder select = new StringBuilder("SELECT u.subject_id");
+
+        for (OrderBySupport.Item obs : orderBySupport.items) {
+            select.append(',').append(obs.select);
+        }
+        select.append(" FROM ");
+
+        return select;
+    }
+
+    private StringBuilder buildWhere(final OrderBySupport orderBySupport, final SubjectType type) {
+        final StringBuilder where = new StringBuilder(" u");
+        for (SearchSupport.SearchView searchView : orderBySupport.views) {
+            where.append(',').append(searchView.name).append(' ').append(searchView.alias);
+        }
+        where.append(" WHERE ");
+        for (SearchSupport.SearchView searchView : orderBySupport.views) {
+            where.append("u.subject_id=").append(searchView.alias).append(".subject_id AND ");
+        }
+
+        for (OrderBySupport.Item obs : orderBySupport.items) {
+            if (StringUtils.isNotBlank(obs.where)) {
+                where.append(obs.where).append(" AND ");
+            }
+        }
+        where.append("u.subject_id NOT IN (");
+
+        return where;
+    }
+
+    private StringBuilder buildOrderBy(final OrderBySupport orderBySupport) {
+        final StringBuilder orderBy = new StringBuilder();
+
+        for (OrderBySupport.Item obs : orderBySupport.items) {
+            orderBy.append(obs.orderBy).append(',');
+        }
+        if (!orderBySupport.items.isEmpty()) {
+            orderBy.insert(0, " ORDER BY ");
+            orderBy.deleteCharAt(orderBy.length() - 1);
+        }
+
+        return orderBy;
+    }
+
+    private OrderBySupport parseOrderBy(final SubjectType type, final SearchSupport svs,
+            final List<OrderByClause> orderByClauses) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
+
+        OrderBySupport orderBySupport = new OrderBySupport();
+
+        for (OrderByClause clause : orderByClauses) {
+            OrderBySupport.Item obs = new OrderBySupport.Item();
+
+            Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), clause.getField());
+            if (subjectField == null) {
+                PlainSchema schema = schemaDAO.find(clause.getField(), attrUtil.plainSchemaClass());
+                if (schema != null) {
+                    if (schema.isUniqueConstraint()) {
+                        orderBySupport.views.add(svs.uniqueAttr());
+
+                        obs.select = new StringBuilder().
+                                append(svs.uniqueAttr().alias).append('.').append(svs.fieldName(schema.getType())).
+                                append(" AS ").append(clause.getField()).toString();
+                        obs.where = new StringBuilder().
+                                append(svs.uniqueAttr().alias).
+                                append(".schema_name='").append(clause.getField()).append("'").toString();
+                        obs.orderBy = clause.getField() + " " + clause.getDirection().name();
+                    } else {
+                        orderBySupport.views.add(svs.attr());
+
+                        obs.select = new StringBuilder().
+                                append(svs.attr().alias).append('.').append(svs.fieldName(schema.getType())).
+                                append(" AS ").append(clause.getField()).toString();
+                        obs.where = new StringBuilder().
+                                append(svs.attr().alias).
+                                append(".schema_name='").append(clause.getField()).append("'").toString();
+                        obs.orderBy = clause.getField() + " " + clause.getDirection().name();
+                    }
+                }
+            } else {
+                orderBySupport.views.add(svs.field());
+
+                obs.select = svs.field().alias + "." + clause.getField();
+                obs.where = StringUtils.EMPTY;
+                obs.orderBy = svs.field().alias + "." + clause.getField() + " " + clause.getDirection().name();
+            }
+
+            if (obs.isEmpty()) {
+                LOG.warn("Cannot build any valid clause from {}", clause);
+            } else {
+                orderBySupport.items.add(obs);
+            }
+        }
+
+        return orderBySupport;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T extends Subject<?, ?, ?>> List<T> doSearch(final Set<Long> adminRoles,
+            final SearchCond nodeCond, final int page, final int itemsPerPage, final List<OrderByClause> orderBy,
+            final SubjectType type) {
+
+        List<Object> parameters = Collections.synchronizedList(new ArrayList<>());
+
+        // 1. get the query string from the search condition
+        SearchSupport svs = new SearchSupport(type);
+        StringBuilder queryString = getQuery(nodeCond, parameters, type, svs);
+
+        // 2. take into account administrative roles and ordering
+        OrderBySupport orderBySupport = parseOrderBy(type, svs, orderBy);
+        if (queryString.charAt(0) == '(') {
+            queryString.insert(0, buildSelect(orderBySupport));
+            queryString.append(buildWhere(orderBySupport, type));
+        } else {
+            queryString.insert(0, buildSelect(orderBySupport).append('('));
+            queryString.append(')').append(buildWhere(orderBySupport, type));
+        }
+        queryString.
+                append(getAdminRolesFilter(adminRoles, type)).append(')').
+                append(buildOrderBy(orderBySupport));
+
+        // 3. prepare the search query
+        Query query = entityManager.createNativeQuery(queryString.toString());
+
+        // 4. page starts from 1, while setFirtResult() starts from 0
+        query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
+
+        if (itemsPerPage >= 0) {
+            query.setMaxResults(itemsPerPage);
+        }
+
+        // 5. populate the search query with parameter values
+        fillWithParameters(query, parameters);
+
+        LOG.debug("Native query\n{}\nwith parameters\n{}", queryString.toString(), parameters);
+
+        // 6. Prepare the result (avoiding duplicates)
+        List<T> result = new ArrayList<>();
+
+        for (Object subjectId : query.getResultList()) {
+            long actualId;
+            if (subjectId instanceof Object[]) {
+                actualId = ((Number) ((Object[]) subjectId)[0]).longValue();
+            } else {
+                actualId = ((Number) subjectId).longValue();
+            }
+
+            T subject = type == SubjectType.USER
+                    ? (T) userDAO.find(actualId)
+                    : (T) roleDAO.find(actualId);
+            if (subject == null) {
+                LOG.error("Could not find {} with id {}, even though returned by the native query",
+                        type, actualId);
+            } else {
+                if (!result.contains(subject)) {
+                    result.add(subject);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private StringBuilder getQuery(final SearchCond nodeCond, final List<Object> parameters,
+            final SubjectType type, final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder();
+
+        switch (nodeCond.getType()) {
+
+            case LEAF:
+            case NOT_LEAF:
+                if (nodeCond.getMembershipCond() != null && SubjectType.USER == type) {
+                    query.append(getQuery(nodeCond.getMembershipCond(),
+                            nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs));
+                }
+                if (nodeCond.getResourceCond() != null) {
+                    query.append(getQuery(nodeCond.getResourceCond(),
+                            nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs));
+                }
+                if (nodeCond.getEntitlementCond() != null) {
+                    query.append(getQuery(nodeCond.getEntitlementCond(),
+                            nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs));
+                }
+                if (nodeCond.getAttributeCond() != null) {
+                    query.append(getQuery(nodeCond.getAttributeCond(),
+                            nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs));
+                }
+                if (nodeCond.getSubjectCond() != null) {
+                    query.append(getQuery(nodeCond.getSubjectCond(),
+                            nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs));
+                }
+                break;
+
+            case AND:
+                query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, type, svs)).
+                        append(" AND subject_id IN ( ").
+                        append(getQuery(nodeCond.getRightNodeCond(), parameters, type, svs)).
+                        append(")");
+                break;
+
+            case OR:
+                query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, type, svs)).
+                        append(" OR subject_id IN ( ").
+                        append(getQuery(nodeCond.getRightNodeCond(), parameters, type, svs)).
+                        append(")");
+                break;
+
+            default:
+        }
+
+        return query;
+    }
+
+    private String getQuery(final MembershipCond cond, final boolean not, final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
+                append(svs.field().name).append(" WHERE ");
+
+        if (not) {
+            query.append("subject_id NOT IN (");
+        } else {
+            query.append("subject_id IN (");
+        }
+
+        query.append("SELECT DISTINCT subject_id ").append("FROM ").
+                append(svs.membership().name).append(" WHERE ").
+                append("role_id=?").append(setParameter(parameters, cond.getRoleId())).
+                append(')');
+
+        return query.toString();
+    }
+
+    private String getQuery(final ResourceCond cond, final boolean not, final List<Object> parameters,
+            final SubjectType type, final SearchSupport svs) {
+
+        final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
+                append(svs.field().name).append(" WHERE ");
+
+        if (not) {
+            query.append("subject_id NOT IN (");
+        } else {
+            query.append("subject_id IN (");
+        }
+
+        query.append("SELECT DISTINCT subject_id FROM ").
+                append(svs.resource().name).
+                append(" WHERE resource_name=?").
+                append(setParameter(parameters, cond.getResourceName()));
+
+        if (type == SubjectType.USER) {
+            query.append(" UNION SELECT DISTINCT subject_id FROM ").
+                    append(svs.roleResource().name).
+                    append(" WHERE resource_name=?").
+                    append(setParameter(parameters, cond.getResourceName()));
+        }
+
+        query.append(')');
+
+        return query.toString();
+    }
+
+    private String getQuery(final EntitlementCond cond, final boolean not, final List<Object> parameters,
+            final SearchSupport svs) {
+
+        final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
+                append(svs.entitlements().name).
+                append(" WHERE entitlement_name ");
+        if (not) {
+            query.append(" NOT ");
+        }
+        query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression()));
+
+        return query.toString();
+    }
+
+    private void fillAttributeQuery(final StringBuilder query, final PlainAttrValue attrValue,
+            final PlainSchema schema, final AttributeCond cond, final boolean not,
+            final List<Object> parameters, final SearchSupport svs) {
+
+        String column = (cond instanceof SubjectCond)
+                ? cond.getSchema()
+                : "' AND " + svs.fieldName(schema.getType());
+
+        switch (cond.getType()) {
+
+            case ISNULL:
+                query.append(column).append(not
+                        ? " IS NOT NULL"
+                        : " IS NULL");
+                break;
+
+            case ISNOTNULL:
+                query.append(column).append(not
+                        ? " IS NULL"
+                        : " IS NOT NULL");
+                break;
+
+            case LIKE:
+                if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
+                    query.append(column);
+                    if (not) {
+                        query.append(" NOT ");
+                    }
+                    query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression()));
+                } else {
+                    if (!(cond instanceof SubjectCond)) {
+                        query.append("' AND");
+                    }
+                    query.append(" 1=2");
+                    LOG.error("LIKE is only compatible with string or enum schemas");
+                }
+                break;
+
+            case EQ:
+                query.append(column);
+                if (not) {
+                    query.append("<>");
+                } else {
+                    query.append('=');
+                }
+                query.append('?').append(setParameter(parameters, attrValue.getValue()));
+                break;
+
+            case GE:
+                query.append(column);
+                if (not) {
+                    query.append('<');
+                } else {
+                    query.append(">=");
+                }
+                query.append('?').append(setParameter(parameters, attrValue.getValue()));
+                break;
+
+            case GT:
+                query.append(column);
+                if (not) {
+                    query.append("<=");
+                } else {
+                    query.append('>');
+                }
+                query.append('?').append(setParameter(parameters, attrValue.getValue()));
+                break;
+
+            case LE:
+                query.append(column);
+                if (not) {
+                    query.append('>');
+                } else {
+                    query.append("<=");
+                }
+                query.append('?').append(setParameter(parameters, attrValue.getValue()));
+                break;
+
+            case LT:
+                query.append(column);
+                if (not) {
+                    query.append(">=");
+                } else {
+                    query.append('<');
+                }
+                query.append('?').append(setParameter(parameters, attrValue.getValue()));
+                break;
+
+            default:
+        }
+    }
+
+    private String getQuery(final AttributeCond cond, final boolean not, final List<Object> parameters,
+            final SubjectType type, final SearchSupport svs) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
+
+        PlainSchema schema = schemaDAO.find(cond.getSchema(), attrUtil.plainSchemaClass());
+        if (schema == null) {
+            LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
+            return EMPTY_ATTR_QUERY;
+        }
+
+        PlainAttrValue attrValue = attrUtil.newPlainAttrValue();
+        try {
+            if (cond.getType() != AttributeCond.Type.LIKE && cond.getType() != AttributeCond.Type.ISNULL
+                    && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+
+                schema.getValidator().validate(cond.getExpression(), attrValue);
+            }
+        } catch (ValidationException e) {
+            LOG.error("Could not validate expression '" + cond.getExpression() + "'", e);
+            return EMPTY_ATTR_QUERY;
+        }
+
+        StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ");
+        if (cond.getType() == AttributeCond.Type.ISNOTNULL) {
+            query.append(svs.field().name).
+                    append(" WHERE subject_id NOT IN (SELECT subject_id FROM ").
+                    append(svs.nullAttr().name).
+                    append(" WHERE schema_name='").append(schema.getKey()).append("')");
+        } else {
+            if (cond.getType() == AttributeCond.Type.ISNULL) {
+                query.append(svs.nullAttr().name).
+                        append(" WHERE schema_name='").append(schema.getKey()).append("'");
+            } else {
+                if (schema.isUniqueConstraint()) {
+                    query.append(svs.uniqueAttr().name);
+                } else {
+                    query.append(svs.attr().name);
+                }
+                query.append(" WHERE schema_name='").append(schema.getKey());
+
+                fillAttributeQuery(query, attrValue, schema, cond, not, parameters, svs);
+            }
+        }
+
+        return query.toString();
+    }
+
+    @SuppressWarnings("rawtypes")
+    private String getQuery(final SubjectCond cond, final boolean not, final List<Object> parameters,
+            final SubjectType type, final SearchSupport svs) {
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType());
+
+        Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), cond.getSchema());
+        if (subjectField == null) {
+            LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
+            return EMPTY_ATTR_QUERY;
+        }
+
+        PlainSchema schema = attrUtil.newPlainSchema();
+        schema.setKey(subjectField.getName());
+        for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
+            if (subjectField.getType().isAssignableFrom(attrSchemaType.getType())) {
+                schema.setType(attrSchemaType);
+            }
+        }
+
+        // Deal with subject Integer fields logically mapping to boolean values
+        // (SyncopeRole.inheritPlainAttrs, for example)
+        boolean foundBooleanMin = false;
+        boolean foundBooleanMax = false;
+        if (Integer.class.equals(subjectField.getType())) {
+            for (Annotation annotation : subjectField.getAnnotations()) {
+                if (Min.class.equals(annotation.annotationType())) {
+                    foundBooleanMin = ((Min) annotation).value() == 0;
+                } else if (Max.class.equals(annotation.annotationType())) {
+                    foundBooleanMax = ((Max) annotation).value() == 1;
+                }
+            }
+        }
+        if (foundBooleanMin && foundBooleanMax) {
+            schema.setType(AttrSchemaType.Boolean);
+        }
+
+        // Deal with subject fields representing relationships to other entities
+        if (subjectField.getType().getAnnotation(Entity.class) != null) {
+            if (BeanUtils.findDeclaredMethodWithMinimalParameters(subjectField.getType(), "getId") != null) {
+                cond.setSchema(cond.getSchema() + "_id");
+                schema.setType(AttrSchemaType.Long);
+            }
+            if (BeanUtils.findDeclaredMethodWithMinimalParameters(subjectField.getType(), "getName") != null) {
+                cond.setSchema(cond.getSchema() + "_name");
+                schema.setType(AttrSchemaType.String);
+            }
+        }
+
+        PlainAttrValue attrValue = attrUtil.newPlainAttrValue();
+        if (cond.getType() != AttributeCond.Type.LIKE
+                && cond.getType() != AttributeCond.Type.ISNULL
+                && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+
+            try {
+                schema.getValidator().validate(cond.getExpression(), attrValue);
+            } catch (ValidationException e) {
+                LOG.error("Could not validate expression '" + cond.getExpression() + "'", e);
+                return EMPTY_ATTR_QUERY;
+            }
+        }
+
+        final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM ").
+                append(svs.field().name).append(" WHERE ");
+
+        fillAttributeQuery(query, attrValue, schema, cond, not, parameters, svs);
+
+        return query.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskDAO.java
new file mode 100644
index 0000000..954697c
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskDAO.java
@@ -0,0 +1,185 @@
+/*
+ * 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.persistence.jpa.dao;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.persistence.Query;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.server.persistence.api.dao.TaskDAO;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.task.Task;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPANotificationTask;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPAPropagationTask;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPAPushTask;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPASchedTask;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPASyncTask;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPATask;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPATaskDAO extends AbstractDAO<Task, Long> implements TaskDAO {
+
+    @Override
+    public Class<? extends Task> getEntityReference(final TaskType type) {
+        Class<? extends Task> result = null;
+
+        switch (type) {
+            case NOTIFICATION:
+                result = JPANotificationTask.class;
+                break;
+
+            case PROPAGATION:
+                result = JPAPropagationTask.class;
+                break;
+
+            case PUSH:
+                result = JPAPushTask.class;
+                break;
+
+            case SCHEDULED:
+                result = JPASchedTask.class;
+                break;
+
+            case SYNCHRONIZATION:
+                result = JPASyncTask.class;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    @Transactional(readOnly = true)
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends Task> T find(final Long key) {
+        return (T) entityManager.find(JPATask.class, key);
+    }
+
+    private <T extends Task> StringBuilder buildfindAllQuery(final TaskType type) {
+        return new StringBuilder("SELECT e FROM ").
+                append(getEntityReference(type).getSimpleName()).
+                append(" e WHERE e.type=:type ");
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Task> List<T> findToExec(final TaskType type) {
+        StringBuilder queryString = buildfindAllQuery(type).append("AND ");
+
+        if (type == TaskType.NOTIFICATION) {
+            queryString.append("e.executed = 0 ");
+        } else {
+            queryString.append("e.executions IS EMPTY ");
+        }
+        queryString.append("ORDER BY e.id DESC");
+
+        Query query = entityManager.createQuery(queryString.toString());
+        query.setParameter("type", type);
+        return query.getResultList();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Task> List<T> findAll(final ExternalResource resource, final TaskType type) {
+        StringBuilder queryString = buildfindAllQuery(type).append("AND e.resource=:resource ORDER BY e.id DESC");
+
+        final Query query = entityManager.createQuery(queryString.toString());
+        query.setParameter("type", type);
+        query.setParameter("resource", resource);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends Task> List<T> findAll(final TaskType type) {
+        return findAll(-1, -1, Collections.<OrderByClause>emptyList(), type);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Task> List<T> findAll(final int page, final int itemsPerPage,
+            final List<OrderByClause> orderByClauses, final TaskType type) {
+
+        StringBuilder queryString = buildfindAllQuery(type);
+        queryString.append(orderByClauses.isEmpty()
+                ? "ORDER BY e.id DESC"
+                : toOrderByStatement(getEntityReference(type), "e", orderByClauses));
+
+        Query query = entityManager.createQuery(queryString.toString());
+        query.setParameter("type", type);
+
+        query.setFirstResult(itemsPerPage * (page <= 0
+                ? 0
+                : page - 1));
+
+        if (itemsPerPage > 0) {
+            query.setMaxResults(itemsPerPage);
+        }
+
+        return query.getResultList();
+    }
+
+    @Override
+    public int count(final TaskType type) {
+        Query countQuery = entityManager.createNativeQuery("SELECT COUNT(id) FROM Task WHERE TYPE=?1");
+        countQuery.setParameter(1, type.toString());
+        return ((Number) countQuery.getSingleResult()).intValue();
+    }
+
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public <T extends Task> T save(final T task) {
+        return entityManager.merge(task);
+    }
+
+    @Override
+    public void delete(final Long id) {
+        Task task = find(id);
+        if (task == null) {
+            return;
+        }
+
+        delete(task);
+    }
+
+    @Override
+    public void delete(final Task task) {
+        entityManager.remove(task);
+    }
+
+    @Override
+    public void deleteAll(final ExternalResource resource, final TaskType type) {
+        List<Task> tasks = findAll(resource, type);
+        if (tasks != null) {
+            List<Long> taskIds = new ArrayList<>(tasks.size());
+            for (Task task : tasks) {
+                taskIds.add(task.getKey());
+            }
+            for (Long taskId : taskIds) {
+                delete(taskId);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskExecDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskExecDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskExecDAO.java
new file mode 100644
index 0000000..7abab11
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPATaskExecDAO.java
@@ -0,0 +1,118 @@
+/*
+ * 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.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.server.persistence.api.dao.TaskDAO;
+import org.apache.syncope.server.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.server.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.server.persistence.api.entity.task.Task;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.persistence.jpa.entity.task.JPATaskExec;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPATaskExecDAO extends AbstractDAO<TaskExec, Long> implements TaskExecDAO {
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Override
+    public TaskExec find(final Long key) {
+        return entityManager.find(JPATaskExec.class, key);
+    }
+
+    private <T extends Task> TaskExec findLatest(final T task, final String field) {
+        TypedQuery<TaskExec> query = entityManager.createQuery(
+                "SELECT e FROM " + JPATaskExec.class.getSimpleName() + " e "
+                + "WHERE e.task=:task "
+                + "ORDER BY e." + field + " DESC", TaskExec.class);
+        query.setParameter("task", task);
+        query.setMaxResults(1);
+
+        List<TaskExec> result = query.getResultList();
+        return result == null || result.isEmpty()
+                ? null
+                : result.iterator().next();
+    }
+
+    @Override
+    public <T extends Task> TaskExec findLatestStarted(final T task) {
+        return findLatest(task, "startDate");
+    }
+
+    @Override
+    public <T extends Task> TaskExec findLatestEnded(final T task) {
+        return findLatest(task, "endDate");
+    }
+
+    @Override
+    public List<TaskExec> findAll(final TaskType type) {
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").append(JPATaskExec.class.getSimpleName()).
+                append(" e WHERE e.task IN (").append("SELECT t FROM ").
+                append(taskDAO.getEntityReference(type).getSimpleName()).append(" t)");
+
+        TypedQuery<TaskExec> query = entityManager.createQuery(queryString.toString(), TaskExec.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public TaskExec save(final TaskExec execution) {
+        return entityManager.merge(execution);
+    }
+
+    /**
+     * This method has an explicit Transactional annotation because it is called by
+     * {@link org.apache.syncope.core.quartz.AbstractTaskJob#execute(org.quartz.JobExecutionContext) }.
+     *
+     * @param taskId task id
+     * @param execution task execution
+     * @throws InvalidEntityException if any bean validation fails
+     */
+    @Override
+    @Transactional(rollbackFor = { Throwable.class })
+    public void saveAndAdd(final Long taskId, final TaskExec execution) throws InvalidEntityException {
+        Task task = taskDAO.find(taskId);
+        task.addExec(execution);
+        taskDAO.save(task);
+    }
+
+    @Override
+    public void delete(final Long id) {
+        TaskExec execution = find(id);
+        if (execution == null) {
+            return;
+        }
+
+        delete(execution);
+    }
+
+    @Override
+    public void delete(final TaskExec execution) {
+        if (execution.getTask() != null) {
+            execution.getTask().removeExec(execution);
+        }
+
+        entityManager.remove(execution);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAUserDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAUserDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAUserDAO.java
new file mode 100644
index 0000000..30fdca5
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAUserDAO.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.server.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.server.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.server.persistence.api.dao.NotFoundException;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.server.persistence.api.dao.search.SubjectCond;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.server.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.persistence.jpa.entity.user.JPAUser;
+import org.apache.syncope.server.misc.security.AuthContextUtil;
+import org.apache.syncope.server.misc.security.UnauthorizedRoleException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAUserDAO extends AbstractSubjectDAO<UPlainAttr, UDerAttr, UVirAttr> implements UserDAO {
+
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    @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);
+    }
+
+    @Override
+    public User find(final Long key) {
+        TypedQuery<User> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAUser.class.getSimpleName() + " e WHERE e.id = :id", User.class);
+        query.setParameter("id", key);
+
+        User result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No user found with id {}", key, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public User find(final String username) {
+        TypedQuery<User> query = entityManager.createQuery("SELECT e FROM " + JPAUser.class.getSimpleName()
+                + " e WHERE e.username = :username", User.class);
+        query.setParameter("username", username);
+
+        User result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No user found with username {}", username, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public User findByWorkflowId(final String workflowId) {
+        TypedQuery<User> query = entityManager.createQuery("SELECT e FROM " + JPAUser.class.getSimpleName()
+                + " e WHERE e.workflowId = :workflowId", User.class);
+        query.setParameter("workflowId", workflowId);
+
+        User result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No user found with workflow id {}", workflowId, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public User findByToken(final String token) {
+        TypedQuery<User> query = entityManager.createQuery("SELECT e FROM " + JPAUser.class.getSimpleName()
+                + " e WHERE e.token = :token", User.class);
+        query.setParameter("token", token);
+
+        User result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No user found with token {}", token, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<User> findBySecurityQuestion(final SecurityQuestion securityQuestion) {
+        TypedQuery<User> query = entityManager.createQuery("SELECT e FROM " + JPAUser.class.getSimpleName()
+                + " e WHERE e.securityQuestion = :securityQuestion", User.class);
+        query.setParameter("securityQuestion", securityQuestion);
+
+        return query.getResultList();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<User> findByAttrValue(final String schemaName, final UPlainAttrValue attrValue) {
+        return (List<User>) findByAttrValue(
+                schemaName, attrValue, attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public User findByAttrUniqueValue(final String schemaName, final UPlainAttrValue attrUniqueValue) {
+        return (User) findByAttrUniqueValue(schemaName, attrUniqueValue,
+                attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<User> findByDerAttrValue(final String schemaName, final String value) {
+        return (List<User>) findByDerAttrValue(
+                schemaName, value, attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<User> findByResource(final ExternalResource resource) {
+        return (List<User>) findByResource(resource, attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    @Override
+    public final List<User> findAll(final Set<Long> adminRoles, final int page, final int itemsPerPage) {
+        return findAll(adminRoles, page, itemsPerPage, Collections.<OrderByClause>emptyList());
+    }
+
+    private SearchCond getAllMatchingCond() {
+        SubjectCond idCond = new SubjectCond(AttributeCond.Type.ISNOTNULL);
+        idCond.setSchema("id");
+        return SearchCond.getLeafCond(idCond);
+    }
+
+    @Override
+    public List<User> findAll(final Set<Long> adminRoles,
+            final int page, final int itemsPerPage, final List<OrderByClause> orderBy) {
+
+        return searchDAO.search(
+                adminRoles, getAllMatchingCond(), page, itemsPerPage, orderBy, SubjectType.USER);
+    }
+
+    @Override
+    public final int count(final Set<Long> adminRoles) {
+        return searchDAO.count(adminRoles, getAllMatchingCond(), SubjectType.USER);
+    }
+
+    @Override
+    public User save(final User user) {
+        final User merged = entityManager.merge(user);
+        for (VirAttr virAttr : merged.getVirAttrs()) {
+            virAttr.getValues().clear();
+            virAttr.getValues().addAll(user.getVirAttr(virAttr.getSchema().getKey()).getValues());
+        }
+
+        return merged;
+    }
+
+    @Override
+    public void delete(final Long key) {
+        User user = (User) findInternal(key);
+        if (user == null) {
+            return;
+        }
+
+        delete(user);
+    }
+
+    @Override
+    public void delete(final User user) {
+        // Not calling membershipDAO.delete() here because it would try to save this user as well, thus going into
+        // ConcurrentModificationException
+        for (Membership membership : user.getMemberships()) {
+            membership.setUser(null);
+
+            roleDAO.save(membership.getRole());
+            membership.setRole(null);
+
+            entityManager.remove(membership);
+        }
+        user.getMemberships().clear();
+
+        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 authFetch(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 authFetch(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/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirAttrDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirAttrDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirAttrDAO.java
new file mode 100644
index 0000000..c78ce1c
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirAttrDAO.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.server.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.server.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.server.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.server.persistence.jpa.entity.AbstractVirAttr;
+import org.apache.syncope.server.persistence.jpa.entity.membership.JPAMVirAttr;
+import org.apache.syncope.server.persistence.jpa.entity.role.JPARVirAttr;
+import org.apache.syncope.server.persistence.jpa.entity.user.JPAUVirAttr;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAVirAttrDAO extends AbstractDAO<VirAttr, Long> implements VirAttrDAO {
+
+    public <T extends VirAttr> Class<? extends AbstractVirAttr> getJPAEntityReference(
+            final Class<T> reference) {
+
+        return RVirAttr.class.isAssignableFrom(reference)
+                ? JPARVirAttr.class
+                : MVirAttr.class.isAssignableFrom(reference)
+                        ? JPAMVirAttr.class
+                        : UVirAttr.class.isAssignableFrom(reference)
+                                ? JPAUVirAttr.class
+                                : null;
+    }
+
+    @Override
+    public <T extends VirAttr> T find(final Long key, final Class<T> reference) {
+        return reference.cast(entityManager.find(getJPAEntityReference(reference), key));
+    }
+
+    @Override
+    public <T extends VirAttr> List<T> findAll(final Class<T> reference) {
+        TypedQuery<T> query = entityManager.createQuery(
+                "SELECT e FROM " + getJPAEntityReference(reference).getSimpleName() + " e", reference);
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends VirAttr> T save(final T virAttr) {
+        return entityManager.merge(virAttr);
+    }
+
+    @Override
+    public <T extends VirAttr> void delete(final Long key, final Class<T> reference) {
+        T virAttr = find(key, reference);
+        if (virAttr == null) {
+            return;
+        }
+
+        delete(virAttr);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends VirAttr> void delete(final T virAttr) {
+        if (virAttr.getOwner() != null) {
+            ((Attributable<?, ?, T>) virAttr.getOwner()).removeVirAttr(virAttr);
+        }
+
+        entityManager.remove(virAttr);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirSchemaDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirSchemaDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirSchemaDAO.java
new file mode 100644
index 0000000..9dd4d0d
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/JPAVirSchemaDAO.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.persistence.jpa.dao;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.persistence.api.dao.AttrTemplateDAO;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.server.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirSchema;
+import org.apache.syncope.server.persistence.api.entity.role.RVirSchema;
+import org.apache.syncope.server.persistence.api.entity.user.UMappingItem;
+import org.apache.syncope.server.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.server.persistence.jpa.entity.AbstractVirSchema;
+import org.apache.syncope.server.persistence.jpa.entity.membership.JPAMVirSchema;
+import org.apache.syncope.server.persistence.jpa.entity.role.JPARVirSchema;
+import org.apache.syncope.server.persistence.jpa.entity.user.JPAUVirSchema;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAVirSchemaDAO extends AbstractDAO<VirSchema, String> implements VirSchemaDAO {
+
+    @Autowired
+    private VirAttrDAO virAttrDAO;
+
+    @Autowired
+    private AttrTemplateDAO<VirSchema> attrTemplateDAO;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    private <T extends VirSchema> Class<? extends AbstractVirSchema> getJPAEntityReference(final Class<T> reference) {
+        return RVirSchema.class.isAssignableFrom(reference)
+                ? JPARVirSchema.class
+                : MVirSchema.class.isAssignableFrom(reference)
+                        ? JPAMVirSchema.class
+                        : UVirSchema.class.isAssignableFrom(reference)
+                                ? JPAUVirSchema.class
+                                : null;
+    }
+
+    @Override
+    public <T extends VirSchema> T find(final String key, final Class<T> reference) {
+        return reference.cast(entityManager.find(getJPAEntityReference(reference), key));
+    }
+
+    @Override
+    public <T extends VirSchema> List<T> findAll(final Class<T> reference) {
+        TypedQuery<T> query = entityManager.createQuery(
+                "SELECT e FROM " + getJPAEntityReference(reference).getSimpleName() + " e", reference);
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends VirAttr> List<T> findAttrs(final VirSchema schema, final Class<T> reference) {
+        final StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+                append(((JPAVirAttrDAO) virAttrDAO).getJPAEntityReference(reference).getSimpleName()).
+                append(" e WHERE e.");
+        if (UVirAttr.class.isAssignableFrom(reference)) {
+            queryString.append("virSchema");
+        } else {
+            queryString.append("template.schema");
+        }
+        queryString.append("=:schema");
+
+        TypedQuery<T> query = entityManager.createQuery(queryString.toString(), reference);
+        query.setParameter("schema", schema);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends VirSchema> T save(final T virSchema) {
+        return entityManager.merge(virSchema);
+    }
+
+    @Override
+    public void delete(final String name, final AttributableUtil attributableUtil) {
+        final VirSchema schema = find(name, attributableUtil.virSchemaClass());
+        if (schema == null) {
+            return;
+        }
+
+        final Set<Long> attrIds = new HashSet<>();
+        for (VirAttr attr : findAttrs(schema, attributableUtil.virAttrClass())) {
+            attrIds.add(attr.getKey());
+        }
+        for (Long attrId : attrIds) {
+            virAttrDAO.delete(attrId, attributableUtil.virAttrClass());
+        }
+
+        if (attributableUtil.getType() != AttributableType.USER) {
+            for (Iterator<Number> it = attrTemplateDAO.
+                    findBySchemaName(schema.getKey(), attributableUtil.virAttrTemplateClass()).iterator();
+                    it.hasNext();) {
+
+                attrTemplateDAO.delete(it.next().longValue(), attributableUtil.virAttrTemplateClass());
+            }
+        }
+
+        resourceDAO.deleteMapping(name, attributableUtil.virIntMappingType(), UMappingItem.class);
+
+        entityManager.remove(schema);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/OrderBySupport.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/OrderBySupport.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/OrderBySupport.java
new file mode 100644
index 0000000..3a80463
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/OrderBySupport.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.persistence.jpa.dao;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class OrderBySupport {
+
+    static class Item {
+
+        protected String select;
+
+        protected String where;
+
+        protected String orderBy;
+
+        protected boolean isEmpty() {
+            return (select == null || select.isEmpty())
+                    && (where == null || where.isEmpty())
+                    && (orderBy == null || orderBy.isEmpty());
+        }
+    }
+
+    protected Set<SearchSupport.SearchView> views = new HashSet<>();
+
+    protected List<Item> items = new ArrayList<>();
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/SearchSupport.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/SearchSupport.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/SearchSupport.java
new file mode 100644
index 0000000..81e9095
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/dao/SearchSupport.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.server.persistence.jpa.dao;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.SubjectType;
+
+class SearchSupport {
+
+    static class SearchView {
+
+        protected String alias;
+
+        protected String name;
+
+        protected SearchView(final String alias, final String name) {
+            this.alias = alias;
+            this.name = name;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return EqualsBuilder.reflectionEquals(this, obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return HashCodeBuilder.reflectionHashCode(this);
+        }
+    }
+
+    private final SubjectType type;
+
+    public SearchSupport(final SubjectType type) {
+        this.type = type;
+    }
+
+    public String fieldName(final AttrSchemaType type) {
+        String result;
+
+        switch (type) {
+            case Boolean:
+                result = "booleanvalue";
+                break;
+
+            case Date:
+                result = "datevalue";
+                break;
+
+            case Double:
+                result = "doublevalue";
+                break;
+
+            case Long:
+                result = "longvalue";
+                break;
+
+            case String:
+            case Enum:
+                result = "stringvalue";
+                break;
+
+            default:
+                result = null;
+        }
+
+        return result;
+    }
+
+    public SearchView field() {
+        String result = "";
+
+        switch (type) {
+            case USER:
+            default:
+                result = "user_search";
+                break;
+
+            case ROLE:
+                result = "role_search";
+                break;
+        }
+
+        return new SearchView("sv", result);
+    }
+
+    public SearchView attr() {
+        return new SearchView("sva", field().name + "_attr");
+    }
+
+    public SearchView membership() {
+        return new SearchView("svm", field().name + "_membership");
+    }
+
+    public SearchView nullAttr() {
+        return new SearchView("svna", field().name + "_null_attr");
+    }
+
+    public SearchView resource() {
+        return new SearchView("svr", field().name + "_resource");
+    }
+
+    public SearchView roleResource() {
+        return new SearchView("svrr", field().name + "_role_resource");
+    }
+
+    public SearchView uniqueAttr() {
+        return new SearchView("svua", field().name + "_unique_attr");
+    }
+
+    public SearchView entitlements() {
+        return new SearchView("sve", field().name + "_entitlements");
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAnnotatedEntity.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAnnotatedEntity.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAnnotatedEntity.java
new file mode 100644
index 0000000..99fd2f3
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAnnotatedEntity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.persistence.jpa.entity;
+
+import java.util.Date;
+import javax.persistence.Column;
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import org.apache.syncope.server.persistence.api.entity.AnnotatedEntity;
+
+/**
+ * Abstract wrapper for common system information.
+ */
+@MappedSuperclass
+@EntityListeners(value = AnnotatedEntityListener.class)
+public abstract class AbstractAnnotatedEntity<KEY> extends AbstractEntity<KEY> implements AnnotatedEntity<KEY> {
+
+    private static final long serialVersionUID = -4801685541488201219L;
+
+    /**
+     * Username of the user that has created this profile.
+     * <br/>
+     * Reference to existing user cannot be used: the creator can either be <tt>admin</tt> or was deleted.
+     */
+    @Column(nullable = false)
+    private String creator;
+
+    /**
+     * Creation date.
+     */
+    @Column(nullable = false)
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date creationDate;
+
+    /**
+     * Username of the user that has performed the last modification to this profile.
+     * <br/>
+     * This field cannot be null: at creation time it needs to be initialized with the creator username.
+     * <br/>
+     * The modifier can be the user itself if the last performed change was a self-modification.
+     * <br/>
+     * Reference to existing user cannot be used: the creator can either be <tt>admin</tt> or was deleted.
+     */
+    @Column(nullable = false)
+    private String lastModifier;
+
+    /**
+     * Last change date.
+     * <br/>
+     * This field cannot be null: at creation time it needs to be initialized with <tt>creationDate</tt> field value.
+     */
+    @Column(nullable = false)
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date lastChangeDate;
+
+    @Override
+    public String getCreator() {
+        return creator;
+    }
+
+    @Override
+    public void setCreator(final String creator) {
+        this.creator = creator;
+    }
+
+    @Override
+    public Date getCreationDate() {
+        return creationDate == null ? null : new Date(creationDate.getTime());
+    }
+
+    @Override
+    public void setCreationDate(final Date creationDate) {
+        this.creationDate = creationDate == null ? null : new Date(creationDate.getTime());
+    }
+
+    @Override
+    public String getLastModifier() {
+        return lastModifier;
+    }
+
+    @Override
+    public void setLastModifier(final String lastModifier) {
+        this.lastModifier = lastModifier;
+    }
+
+    @Override
+    public Date getLastChangeDate() {
+        return lastChangeDate == null ? creationDate : lastChangeDate;
+    }
+
+    @Override
+    public void setLastChangeDate(final Date lastChangeDate) {
+        this.lastChangeDate = lastChangeDate == null ? null : new Date(lastChangeDate.getTime());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttrTemplate.java
new file mode 100644
index 0000000..3c79c92
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttrTemplate.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.persistence.jpa.entity;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.server.persistence.api.entity.AttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.Schema;
+
+@MappedSuperclass
+public abstract class AbstractAttrTemplate<S extends Schema> extends AbstractEntity<Long> implements AttrTemplate<S> {
+
+    private static final long serialVersionUID = 4829112252713766666L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    protected Long id;
+
+    @Override
+    public Long getKey() {
+        return id;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttributable.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttributable.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttributable.java
new file mode 100644
index 0000000..ea53906
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractAttributable.java
@@ -0,0 +1,106 @@
+/*
+ * 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.persistence.jpa.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.DerSchema;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.VirSchema;
+
+public abstract class AbstractAttributable<P extends PlainAttr, D extends DerAttr, V extends VirAttr>
+        extends AbstractAnnotatedEntity<Long> implements Attributable<P, D, V> {
+
+    private static final long serialVersionUID = -4801685541488201119L;
+
+    @Override
+    public P getPlainAttr(final String plainSchemaName) {
+        P result = null;
+        for (P plainAttr : getPlainAttrs()) {
+            if (plainAttr != null && plainAttr.getSchema() != null
+                    && plainSchemaName.equals(plainAttr.getSchema().getKey())) {
+
+                result = plainAttr;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public D getDerAttr(final String derSchemaName) {
+        D result = null;
+        for (D derAttr : getDerAttrs()) {
+            if (derAttr != null && derAttr.getSchema() != null
+                    && derSchemaName.equals(derAttr.getSchema().getKey())) {
+
+                result = derAttr;
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public V getVirAttr(final String virSchemaName) {
+        V result = null;
+        for (V virAttr : getVirAttrs()) {
+            if (virAttr != null && virAttr.getSchema() != null
+                    && virSchemaName.equals(virAttr.getSchema().getKey())) {
+
+                result = virAttr;
+            }
+        }
+
+        return result;
+    }
+
+    protected Map<PlainSchema, P> getPlainAttrMap() {
+        final Map<PlainSchema, P> map = new HashMap<>();
+
+        for (P attr : getPlainAttrs()) {
+            map.put(attr.getSchema(), attr);
+        }
+
+        return map;
+    }
+
+    protected Map<DerSchema, D> getDerAttrMap() {
+        final Map<DerSchema, D> map = new HashMap<>();
+
+        for (D attr : getDerAttrs()) {
+            map.put(attr.getSchema(), attr);
+        }
+
+        return map;
+    }
+
+    protected Map<VirSchema, V> getVirAttrMap() {
+        final Map<VirSchema, V> map = new HashMap<>();
+
+        for (V attr : getVirAttrs()) {
+            map.put(attr.getSchema(), attr);
+        }
+
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerAttr.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerAttr.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerAttr.java
new file mode 100644
index 0000000..bb6dcd3
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerAttr.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.server.persistence.jpa.entity;
+
+import java.util.Collection;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.misc.jexl.JexlUtil;
+
+@MappedSuperclass
+public abstract class AbstractDerAttr extends AbstractEntity<Long> implements DerAttr {
+
+    private static final long serialVersionUID = 4740924251090424771L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    protected Long id;
+
+    @Override
+    public Long getKey() {
+        return id;
+    }
+
+    /**
+     * @param attributes the set of attributes against which evaluate this derived attribute
+     * @return the value of this derived attribute
+     */
+    @Override
+    public String getValue(final Collection<? extends PlainAttr> attributes) {
+        return JexlUtil.evaluate(getSchema().getExpression(), getOwner(), attributes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerSchema.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerSchema.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerSchema.java
new file mode 100644
index 0000000..6e47e9b
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/server/persistence/jpa/entity/AbstractDerSchema.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.server.persistence.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.server.persistence.api.entity.DerSchema;
+import org.apache.syncope.server.persistence.jpa.validation.entity.SchemaNameCheck;
+
+@MappedSuperclass
+@SchemaNameCheck
+public abstract class AbstractDerSchema extends AbstractEntity<String> implements DerSchema {
+
+    private static final long serialVersionUID = -6173643493348674060L;
+
+    @Id
+    private String name;
+
+    @Column(nullable = false)
+    private String expression;
+
+    @Override
+    public String getKey() {
+        return name;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.name = key;
+    }
+
+    @Override
+    public String getExpression() {
+        return expression;
+    }
+
+    @Override
+    public void setExpression(final String expression) {
+        this.expression = expression;
+    }
+
+    @Override
+    public AttrSchemaType getType() {
+        return AttrSchemaType.String;
+    }
+
+    @Override
+    public String getMandatoryCondition() {
+        return Boolean.FALSE.toString().toLowerCase();
+    }
+
+    @Override
+    public boolean isMultivalue() {
+        return Boolean.TRUE;
+    }
+
+    @Override
+    public boolean isUniqueConstraint() {
+        return Boolean.FALSE;
+    }
+
+    @Override
+    public boolean isReadonly() {
+        return Boolean.FALSE;
+    }
+
+}


Mime
View raw message