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/01 19:12:21 UTC

[26/32] syncope git commit: [SYNCOPE-620] JPA entities + basic tests

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskDAO.java
new file mode 100644
index 0000000..d1d9b15
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.jpa.entity.task.JPANotificationTask;
+import org.apache.syncope.persistence.jpa.entity.task.JPAPropagationTask;
+import org.apache.syncope.persistence.jpa.entity.task.JPAPushTask;
+import org.apache.syncope.persistence.jpa.entity.task.JPASchedTask;
+import org.apache.syncope.persistence.jpa.entity.task.JPASyncTask;
+import org.apache.syncope.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskExecDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskExecDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPATaskExecDAO.java
new file mode 100644
index 0000000..cc47a29
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.persistence.api.dao.TaskDAO;
+import org.apache.syncope.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.persistence.api.entity.task.Task;
+import org.apache.syncope.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.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/556d5186/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
new file mode 100644
index 0000000..2c141a6
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAUserDAO.java
@@ -0,0 +1,225 @@
+/*
+ * 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.dao;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+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.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.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.ExternalResource;
+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.user.SecurityQuestion;
+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.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.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;
+
+    @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, JPAAttributableUtil.getInstance(AttributableType.USER));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public User findByAttrUniqueValue(final String schemaName, final UPlainAttrValue attrUniqueValue) {
+        return (User) findByAttrUniqueValue(schemaName, attrUniqueValue,
+                JPAAttributableUtil.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));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<User> findByResource(final ExternalResource resource) {
+        return (List<User>) findByResource(resource, JPAAttributableUtil.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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirAttrDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirAttrDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirAttrDAO.java
new file mode 100644
index 0000000..62a1185
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.jpa.entity.AbstractVirAttr;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMVirAttr;
+import org.apache.syncope.persistence.jpa.entity.role.JPARVirAttr;
+import org.apache.syncope.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) {
+            ((Subject<?, ?, T>) virAttr.getOwner()).removeVirAttr(virAttr);
+        }
+
+        entityManager.remove(virAttr);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirSchemaDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirSchemaDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAVirSchemaDAO.java
new file mode 100644
index 0000000..c0311f2
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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.persistence.api.dao.AttrTemplateDAO;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.persistence.api.entity.membership.MVirSchema;
+import org.apache.syncope.persistence.api.entity.role.RVirSchema;
+import org.apache.syncope.persistence.api.entity.user.UMappingItem;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.persistence.jpa.entity.AbstractVirSchema;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMVirSchema;
+import org.apache.syncope.persistence.jpa.entity.role.JPARVirSchema;
+import org.apache.syncope.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/OrderBySupport.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/OrderBySupport.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/OrderBySupport.java
new file mode 100644
index 0000000..aafaf0e
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/SearchSupport.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/SearchSupport.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/SearchSupport.java
new file mode 100644
index 0000000..2e2c7a7
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAnnotatedEntity.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAnnotatedEntity.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAnnotatedEntity.java
new file mode 100644
index 0000000..1497676
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttrTemplate.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttrTemplate.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttrTemplate.java
new file mode 100644
index 0000000..d3b6590
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.persistence.jpa.entity;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.persistence.api.entity.AttrTemplate;
+import org.apache.syncope.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttributable.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttributable.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractAttributable.java
new file mode 100644
index 0000000..a6398cf
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.persistence.jpa.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+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.VirAttr;
+import org.apache.syncope.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerAttr.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerAttr.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerAttr.java
new file mode 100644
index 0000000..4e1eb4d
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.utils.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/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerSchema.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerSchema.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractDerSchema.java
new file mode 100644
index 0000000..e348aa7
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/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.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.persistence.api.entity.DerSchema;
+import org.apache.syncope.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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/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
new file mode 100644
index 0000000..720b62b
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractEntity.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.persistence.api.entity.Entity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+
+public abstract class AbstractEntity<KEY> implements Entity<KEY>, Serializable {
+
+    private static final long serialVersionUID = -9017214159540857901L;
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractEntity.class);
+
+    protected void checkType(final Object object, final Class<?> clazz) {
+        if (object !=null && !clazz.isInstance(object)) {
+            throw new ClassCastException("Expected " + clazz.getName() + ", got " + object.getClass().getName());
+        }
+    }
+
+    /**
+     * @param property the integer representing a boolean value
+     * @return the boolean value corresponding to the property param
+     */
+    public final boolean isBooleanAsInteger(final Integer property) {
+        return property != null && property == 1;
+    }
+
+    /**
+     * @param value the boolean value to be represented as integer
+     * @return the integer corresponding to the property param
+     */
+    public final Integer getBooleanAsInteger(final Boolean value) {
+        return Boolean.TRUE.equals(value)
+                ? 1
+                : 0;
+    }
+
+    /**
+     * @return fields to be excluded when computing equals() or hashcode()
+     */
+    private String[] getExcludeFields() {
+        Set<String> excludeFields = new HashSet<>();
+
+        for (PropertyDescriptor propDesc : BeanUtils.getPropertyDescriptors(getClass())) {
+            if (propDesc.getPropertyType().isInstance(Collections.emptySet())
+                    || propDesc.getPropertyType().isInstance(Collections.emptyList())) {
+
+                excludeFields.add(propDesc.getName());
+            }
+        }
+
+        return excludeFields.toArray(new String[] {});
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj, getExcludeFields());
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this, getExcludeFields());
+    }
+
+    @Override
+    public String toString() {
+        Method method = BeanUtils.findMethod(getClass(), "getKey");
+
+        StringBuilder result = new StringBuilder().append(getClass().getSimpleName()).append('[');
+        if (method != null) {
+            try {
+                result.append(method.invoke(this));
+            } catch (Exception e) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.error("While serializing to string", e);
+                }
+            }
+        }
+        result.append(']');
+
+        return result.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractExec.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractExec.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractExec.java
new file mode 100644
index 0000000..0d62824
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractExec.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.persistence.jpa.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Lob;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import org.apache.syncope.persistence.api.entity.Exec;
+
+@MappedSuperclass
+public abstract class AbstractExec extends AbstractEntity<Long> implements Exec {
+
+    private static final long serialVersionUID = -812344822970166317L;
+
+    @Column(nullable = false)
+    protected String status;
+
+    /**
+     * Any information to be accompanied to this execution's result.
+     */
+    @Lob
+    protected String message;
+
+    /**
+     * Start instant of this execution.
+     */
+    @Temporal(TemporalType.TIMESTAMP)
+    protected Date startDate;
+
+    /**
+     * End instant of this execution.
+     */
+    @Temporal(TemporalType.TIMESTAMP)
+    protected Date endDate;
+
+    @Override
+    public String getStatus() {
+        return status;
+    }
+
+    @Override
+    public void setStatus(final String status) {
+        this.status = status;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Set a message for this execution, taking care of replacing every null character with newline.
+     *
+     * @param message the message to set for this execution
+     */
+    @Override
+    public void setMessage(String message) {
+        if (message != null) {
+            message = message.replace('\0', '\n');
+        }
+        this.message = message;
+    }
+
+    @Override
+    public Date getEndDate() {
+        return endDate == null
+                ? null
+                : new Date(endDate.getTime());
+    }
+
+    @Override
+
+    public void setEndDate(final Date endDate) {
+        this.endDate = endDate == null
+                ? null
+                : new Date(endDate.getTime());
+    }
+
+    @Override
+
+    public Date getStartDate() {
+        return startDate == null
+                ? null
+                : new Date(startDate.getTime());
+    }
+
+    @Override
+
+    public void setStartDate(final Date startDate) {
+        this.startDate = startDate == null
+                ? null
+                : new Date(startDate.getTime());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMapping.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMapping.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMapping.java
new file mode 100644
index 0000000..f48a2bb
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMapping.java
@@ -0,0 +1,76 @@
+/*
+ * 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 javax.persistence.Cacheable;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.persistence.api.entity.Mapping;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+
+@MappedSuperclass
+@Cacheable
+public abstract class AbstractMapping<T extends MappingItem> extends AbstractEntity<Long> implements Mapping<T> {
+
+    private static final long serialVersionUID = 4316047254916259158L;
+
+    /**
+     * A JEXL expression for determining how to find the account id in external resource's space.
+     */
+    private String accountLink;
+
+    @Override
+    public String getAccountLink() {
+        return accountLink;
+    }
+
+    @Override
+    public void setAccountLink(final String accountLink) {
+        this.accountLink = accountLink;
+    }
+
+    @Override
+    public T getAccountIdItem() {
+        T accountIdItem = null;
+        for (T item : getItems()) {
+            if (item.isAccountid()) {
+                accountIdItem = item;
+            }
+        }
+        return accountIdItem;
+    }
+
+    protected 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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMappingItem.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMappingItem.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMappingItem.java
new file mode 100644
index 0000000..7ad5e97
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractMappingItem.java
@@ -0,0 +1,190 @@
+/*
+ * 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 javax.persistence.Basic;
+import javax.persistence.Cacheable;
+import javax.persistence.Column;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.MappedSuperclass;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+
+@MappedSuperclass
+@Cacheable
+public abstract class AbstractMappingItem extends AbstractEntity<Long> implements MappingItem {
+
+    private static final long serialVersionUID = 7383601853619332424L;
+
+    @Column(nullable = true)
+    private String intAttrName;
+
+    @Column(nullable = false)
+    @Enumerated(EnumType.STRING)
+    private IntMappingType intMappingType;
+
+    /**
+     * Target resource's field to be mapped.
+     */
+    @Column(nullable = true)
+    private String extAttrName;
+
+    /**
+     * Specify if the mapped target resource's field is nullable.
+     */
+    @Column(nullable = false)
+    private String mandatoryCondition;
+
+    /**
+     * Specify if the mapped target resource's field is the key.
+     */
+    @Column(nullable = false)
+    @Basic
+    @Min(0)
+    @Max(1)
+    private Integer accountid;
+
+    /**
+     * Specify if the mapped target resource's field is the password.
+     */
+    @Column(nullable = false)
+    @Basic
+    @Min(0)
+    @Max(1)
+    private Integer password;
+
+    /**
+     * Mapping purposes: SYNCHRONIZATION, PROPAGATION, BOTH.
+     */
+    @Column(nullable = false)
+    @Enumerated(EnumType.STRING)
+    private MappingPurpose purpose;
+
+    public AbstractMappingItem() {
+        super();
+
+        mandatoryCondition = Boolean.FALSE.toString();
+
+        accountid = getBooleanAsInteger(false);
+        password = getBooleanAsInteger(false);
+    }
+
+    @Override
+    public String getExtAttrName() {
+        return extAttrName;
+    }
+
+    @Override
+    public void setExtAttrName(final String extAttrName) {
+        this.extAttrName = extAttrName;
+    }
+
+    @Override
+    public String getMandatoryCondition() {
+        return mandatoryCondition;
+    }
+
+    @Override
+    public void setMandatoryCondition(final String mandatoryCondition) {
+        this.mandatoryCondition = mandatoryCondition;
+    }
+
+    @Override
+    public String getIntAttrName() {
+        final String name;
+
+        switch (getIntMappingType()) {
+            case UserId:
+            case RoleId:
+            case MembershipId:
+                name = "id";
+                break;
+
+            case Username:
+                name = "username";
+                break;
+
+            case Password:
+                name = "password";
+                break;
+
+            case RoleName:
+                name = "roleName";
+                break;
+
+            case RoleOwnerSchema:
+                name = "roleOwnerSchema";
+                break;
+
+            default:
+                name = intAttrName;
+        }
+
+        return name;
+    }
+
+    @Override
+    public void setIntAttrName(final String intAttrName) {
+        this.intAttrName = intAttrName;
+    }
+
+    @Override
+    public IntMappingType getIntMappingType() {
+        return intMappingType;
+    }
+
+    @Override
+    public void setIntMappingType(final IntMappingType intMappingType) {
+        this.intMappingType = intMappingType;
+    }
+
+    @Override
+    public boolean isAccountid() {
+        return isBooleanAsInteger(accountid);
+    }
+
+    @Override
+    public void setAccountid(final boolean accountid) {
+        this.accountid = getBooleanAsInteger(accountid);
+    }
+
+    @Override
+    public boolean isPassword() {
+        return isBooleanAsInteger(password);
+    }
+
+    @Override
+    public void setPassword(final boolean password) {
+        this.password = getBooleanAsInteger(password);
+    }
+
+    @Override
+    public MappingPurpose getPurpose() {
+        return purpose;
+    }
+
+    @Override
+    public void setPurpose(final MappingPurpose purpose) {
+        this.purpose = purpose;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttr.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttr.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttr.java
new file mode 100644
index 0000000..4c8f2db
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttr.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttrUniqueValue;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.jpa.validation.entity.PlainAttrCheck;
+
+@MappedSuperclass
+@PlainAttrCheck
+public abstract class AbstractPlainAttr extends AbstractEntity<Long> implements PlainAttr {
+
+    private static final long serialVersionUID = -9115431608821806124L;
+
+    protected abstract boolean addValue(PlainAttrValue attrValue);
+
+    @Override
+    public void addValue(final String value, final AttributableUtil attributableUtil)
+            throws InvalidPlainAttrValueException {
+
+        PlainAttrValue attrValue;
+        if (getSchema().isUniqueConstraint()) {
+            attrValue = attributableUtil.newPlainAttrUniqueValue();
+            ((PlainAttrUniqueValue) attrValue).setSchema(getSchema());
+        } else {
+            attrValue = attributableUtil.newPlainAttrValue();
+        }
+
+        attrValue.setAttr(this);
+        getSchema().getValidator().validate(value, attrValue);
+
+        if (getSchema().isUniqueConstraint()) {
+            setUniqueValue((PlainAttrUniqueValue) attrValue);
+        } else {
+            if (!getSchema().isMultivalue()) {
+                getValues().clear();
+            }
+            addValue(attrValue);
+        }
+    }
+
+    @Override
+    public List<String> getValuesAsStrings() {
+        List<String> result;
+        if (getUniqueValue() == null) {
+            result = new ArrayList<>(getValues().size());
+            for (PlainAttrValue attributeValue : getValues()) {
+                result.add(attributeValue.getValueAsString());
+            }
+        } else {
+            result = Collections.singletonList(getUniqueValue().getValueAsString());
+        }
+
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttrValue.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttrValue.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttrValue.java
new file mode 100644
index 0000000..282e3da
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/entity/AbstractPlainAttrValue.java
@@ -0,0 +1,282 @@
+/*
+ * 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 java.util.Date;
+import javax.persistence.Basic;
+import javax.persistence.Lob;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.utils.DataFormat;
+import org.apache.syncope.persistence.jpa.validation.entity.PlainAttrValueCheck;
+import org.apache.syncope.server.security.Encryptor;
+
+@MappedSuperclass
+@PlainAttrValueCheck
+public abstract class AbstractPlainAttrValue extends AbstractEntity<Long> implements PlainAttrValue {
+
+    private static final long serialVersionUID = -9141923816611244785L;
+
+    private String stringValue;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date dateValue;
+
+    @Basic
+    @Min(0)
+    @Max(1)
+    private Integer booleanValue;
+
+    private Long longValue;
+
+    private Double doubleValue;
+
+    @Lob
+    private byte[] binaryValue;
+
+    @Override
+    public Boolean getBooleanValue() {
+        return booleanValue == null
+                ? null
+                : isBooleanAsInteger(booleanValue);
+    }
+
+    @Override
+    public void setBooleanValue(final Boolean booleanValue) {
+        this.booleanValue = booleanValue == null
+                ? null
+                : getBooleanAsInteger(booleanValue);
+    }
+
+    @Override
+    public Date getDateValue() {
+        return dateValue == null
+                ? null
+                : new Date(dateValue.getTime());
+    }
+
+    @Override
+    public void setDateValue(final Date dateValue) {
+        this.dateValue = dateValue == null
+                ? null
+                : new Date(dateValue.getTime());
+    }
+
+    @Override
+    public Double getDoubleValue() {
+        return doubleValue;
+    }
+
+    @Override
+    public void setDoubleValue(final Double doubleValue) {
+        this.doubleValue = doubleValue;
+    }
+
+    @Override
+    public Long getLongValue() {
+        return longValue;
+    }
+
+    @Override
+    public void setLongValue(final Long longValue) {
+        this.longValue = longValue;
+    }
+
+    @Override
+    public String getStringValue() {
+        return stringValue;
+    }
+
+    @Override
+    public void setStringValue(final String stringValue) {
+        this.stringValue = stringValue;
+    }
+
+    @Override
+    public byte[] getBinaryValue() {
+        return binaryValue;
+    }
+
+    @Override
+    public void setBinaryValue(final byte[] binaryValue) {
+        this.binaryValue = ArrayUtils.clone(binaryValue);
+    }
+
+    @Override
+    public void parseValue(final PlainSchema schema, final String value) throws ParsingValidationException {
+        Exception exception = null;
+
+        switch (schema.getType()) {
+
+            case Boolean:
+                this.setBooleanValue(Boolean.parseBoolean(value));
+                break;
+
+            case Long:
+                try {
+                    this.setLongValue(schema.getConversionPattern() == null
+                            ? Long.valueOf(value)
+                            : DataFormat.parseNumber(value, schema.getConversionPattern()).longValue());
+                } catch (Exception pe) {
+                    exception = pe;
+                }
+                break;
+
+            case Double:
+                try {
+                    this.setDoubleValue(schema.getConversionPattern() == null
+                            ? Double.valueOf(value)
+                            : DataFormat.parseNumber(value, schema.getConversionPattern()).doubleValue());
+                } catch (Exception pe) {
+                    exception = pe;
+                }
+                break;
+
+            case Date:
+                try {
+                    this.setDateValue(schema.getConversionPattern() == null
+                            ? DataFormat.parseDate(value)
+                            : new Date(DataFormat.parseDate(value, schema.getConversionPattern()).getTime()));
+                } catch (Exception pe) {
+                    exception = pe;
+                }
+                break;
+
+            case Encrypted:
+                try {
+                    this.setStringValue(Encryptor.getInstance(schema.getSecretKey()).
+                            encode(value, schema.getCipherAlgorithm()));
+                } catch (Exception pe) {
+                    exception = pe;
+                }
+                break;
+
+            case Binary:
+                this.setBinaryValue(Base64.decodeBase64(value));
+                break;
+
+            case String:
+            case Enum:
+            default:
+                this.setStringValue(value);
+        }
+
+        if (exception != null) {
+            throw new ParsingValidationException("While trying to parse '" + value + "' as " + schema.getKey(),
+                    exception);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T getValue() {
+        return (T) (booleanValue != null
+                ? getBooleanValue()
+                : dateValue != null
+                        ? getDateValue()
+                        : doubleValue != null
+                                ? getDoubleValue()
+                                : longValue != null
+                                        ? getLongValue()
+                                        : binaryValue != null
+                                                ? getBinaryValue()
+                                                : stringValue);
+    }
+
+    @Override
+    public String getValueAsString() {
+        final AttrSchemaType type = getAttr() == null || getAttr().getSchema() == null
+                || getAttr().getSchema().getType() == null
+                        ? AttrSchemaType.String
+                        : getAttr().getSchema().getType();
+
+        return getValueAsString(type);
+    }
+
+    @Override
+    public String getValueAsString(final AttrSchemaType type) {
+        Exception exception = null;
+
+        String result = null;
+
+        switch (type) {
+
+            case Boolean:
+                result = getBooleanValue().toString();
+                break;
+
+            case Long:
+                result = getAttr() == null || getAttr().getSchema() == null
+                        || getAttr().getSchema().getConversionPattern() == null
+                                ? getLongValue().toString()
+                                : DataFormat.format(getLongValue(), getAttr().getSchema().getConversionPattern());
+                break;
+
+            case Double:
+                result = getAttr() == null || getAttr().getSchema() == null
+                        || getAttr().getSchema().getConversionPattern() == null
+                                ? getDoubleValue().toString()
+                                : DataFormat.format(getDoubleValue(), getAttr().getSchema().getConversionPattern());
+                break;
+
+            case Date:
+                result = getAttr() == null || getAttr().getSchema() == null
+                        || getAttr().getSchema().getConversionPattern() == null
+                                ? DataFormat.format(getDateValue())
+                                : DataFormat.format(getDateValue(), false, getAttr().getSchema().
+                                        getConversionPattern());
+                break;
+
+            case Binary:
+                result = new String(Base64.encodeBase64String(getBinaryValue()));
+                break;
+
+            case String:
+            case Enum:
+            case Encrypted:
+            default:
+                result = getStringValue();
+                break;
+        }
+
+        if (exception != null) {
+            throw new InvalidPlainAttrValueException(
+                    "While trying to format '" + getValue() + "' as " + type, exception);
+        }
+
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+    }
+}