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:22 UTC

[27/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/JPAPlainAttrValueDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainAttrValueDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainAttrValueDAO.java
new file mode 100644
index 0000000..75951cb
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainAttrValueDAO.java
@@ -0,0 +1,104 @@
+/*
+ * 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.PlainAttrValueDAO;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.conf.CPlainAttrUniqueValue;
+import org.apache.syncope.persistence.api.entity.conf.CPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrUniqueValue;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrUniqueValue;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttrUniqueValue;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.persistence.jpa.entity.AbstractPlainAttrValue;
+import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttrUniqueValue;
+import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainAttrValue;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMPlainAttrUniqueValue;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMPlainAttrValue;
+import org.apache.syncope.persistence.jpa.entity.role.JPARPlainAttrUniqueValue;
+import org.apache.syncope.persistence.jpa.entity.role.JPARPlainAttrValue;
+import org.apache.syncope.persistence.jpa.entity.user.JPAUPlainAttrUniqueValue;
+import org.apache.syncope.persistence.jpa.entity.user.JPAUPlainAttrValue;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAPlainAttrValueDAO extends AbstractDAO<PlainAttrValue, Long> implements PlainAttrValueDAO {
+
+    private <T extends PlainAttrValue> Class<? extends AbstractPlainAttrValue> getJPAEntityReference(
+            final Class<T> reference) {
+
+        return reference.equals(CPlainAttrValue.class)
+                ? JPACPlainAttrValue.class
+                : reference.equals(CPlainAttrUniqueValue.class)
+                        ? JPACPlainAttrUniqueValue.class
+                        : reference.equals(RPlainAttrValue.class)
+                                ? JPARPlainAttrValue.class
+                                : reference.equals(RPlainAttrUniqueValue.class)
+                                        ? JPARPlainAttrUniqueValue.class
+                                        : reference.equals(MPlainAttrValue.class)
+                                                ? JPAMPlainAttrValue.class
+                                                : reference.equals(MPlainAttrUniqueValue.class)
+                                                        ? JPAMPlainAttrUniqueValue.class
+                                                        : reference.equals(UPlainAttrValue.class)
+                                                                ? JPAUPlainAttrValue.class
+                                                                : reference.equals(UPlainAttrUniqueValue.class)
+                                                                        ? JPAUPlainAttrUniqueValue.class
+                                                                        : null;
+    }
+
+    @Override
+    public <T extends PlainAttrValue> T find(final Long key, final Class<T> reference) {
+        return reference.cast(entityManager.find(getJPAEntityReference(reference), key));
+    }
+
+    @Override
+    public <T extends PlainAttrValue> 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 PlainAttrValue> T save(final T attributeValue) {
+        return entityManager.merge(attributeValue);
+    }
+
+    @Override
+    public <T extends PlainAttrValue> void delete(final Long id, final Class<T> reference) {
+        T attributeValue = find(id, reference);
+        if (attributeValue == null) {
+            return;
+        }
+
+        delete(attributeValue);
+    }
+
+    @Override
+    public <T extends PlainAttrValue> void delete(final T attrValue) {
+        if (attrValue.getAttr() != null) {
+            attrValue.getAttr().removeValue(attrValue);
+        }
+
+        entityManager.remove(attrValue);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainSchemaDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainSchemaDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainSchemaDAO.java
new file mode 100644
index 0000000..47c5127
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPlainSchemaDAO.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.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.PlainAttrDAO;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.conf.CPlainSchema;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainSchema;
+import org.apache.syncope.persistence.api.entity.role.RMappingItem;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainSchema;
+import org.apache.syncope.persistence.api.entity.user.UMappingItem;
+import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.persistence.jpa.entity.AbstractPlainSchema;
+import org.apache.syncope.persistence.jpa.entity.conf.JPACPlainSchema;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMPlainSchema;
+import org.apache.syncope.persistence.jpa.entity.role.JPARPlainSchema;
+import org.apache.syncope.persistence.jpa.entity.user.JPAUPlainSchema;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAPlainSchemaDAO extends AbstractDAO<PlainSchema, String> implements PlainSchemaDAO {
+
+    @Autowired
+    private PlainAttrDAO attrDAO;
+
+    @Autowired
+    private AttrTemplateDAO<PlainSchema> attrTemplateDAO;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    private <T extends PlainSchema> Class<? extends AbstractPlainSchema> getJPAEntityReference(
+            final Class<T> reference) {
+
+        return CPlainSchema.class.isAssignableFrom(reference)
+                ? JPACPlainSchema.class
+                : RPlainSchema.class.isAssignableFrom(reference)
+                        ? JPARPlainSchema.class
+                        : MPlainSchema.class.isAssignableFrom(reference)
+                                ? JPAMPlainSchema.class
+                                : UPlainSchema.class.isAssignableFrom(reference)
+                                        ? JPAUPlainSchema.class
+                                        : null;
+    }
+
+    @Override
+    public <T extends PlainSchema> T find(final String key, final Class<T> reference) {
+        return reference.cast(entityManager.find(getJPAEntityReference(reference), key));
+    }
+
+    @Override
+    public <T extends PlainSchema> 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 PlainAttr> List<T> findAttrs(final PlainSchema schema, final Class<T> reference) {
+        final StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+                append(((JPAPlainAttrDAO) attrDAO).getJPAEntityReference(reference).getSimpleName()).
+                append(" e WHERE e.");
+        if (RPlainAttr.class.isAssignableFrom(reference) || MPlainAttr.class.isAssignableFrom(reference)) {
+            queryString.append("template.");
+        }
+        queryString.append("schema=:schema");
+
+        TypedQuery<T> query = entityManager.createQuery(queryString.toString(), reference);
+        query.setParameter("schema", schema);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends PlainSchema> T save(final T schema) {
+        return entityManager.merge(schema);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void delete(final String key, final AttributableUtil attributableUtil) {
+        PlainSchema schema = find(key, attributableUtil.plainSchemaClass());
+        if (schema == null) {
+            return;
+        }
+
+        final Set<Long> attrIds = new HashSet<>();
+        for (PlainAttr attr : findAttrs(schema, attributableUtil.plainAttrClass())) {
+            attrIds.add(attr.getKey());
+        }
+        for (Long attrId : attrIds) {
+            attrDAO.delete(attrId, attributableUtil.plainAttrClass());
+        }
+
+        if (attributableUtil.getType() == AttributableType.ROLE
+                || attributableUtil.getType() == AttributableType.MEMBERSHIP) {
+
+            for (Iterator<Number> it = attrTemplateDAO.
+                    findBySchemaName(schema.getKey(), attributableUtil.plainAttrTemplateClass()).iterator();
+                    it.hasNext();) {
+
+                attrTemplateDAO.delete(it.next().longValue(), attributableUtil.plainAttrTemplateClass());
+            }
+        }
+
+        resourceDAO.deleteMapping(key, attributableUtil.intMappingType(), UMappingItem.class);
+        resourceDAO.deleteMapping(key, attributableUtil.intMappingType(), RMappingItem.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/JPAPolicyDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPolicyDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPolicyDAO.java
new file mode 100644
index 0000000..d9aedce
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAPolicyDAO.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.persistence.jpa.entity.JPAPolicy;
+import org.apache.syncope.persistence.jpa.entity.JPAAccountPolicy;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPAPolicyDAO extends AbstractDAO<Policy, Long> implements PolicyDAO {
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Policy> T find(final Long key) {
+        final Query query = entityManager.createQuery(
+                "SELECT e FROM " + JPAPolicy.class.getSimpleName() + " e WHERE e.id=:id");
+        query.setParameter("id", key);
+
+        List<T> result = query.getResultList();
+        return result.isEmpty()
+                ? null
+                : result.iterator().next();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Policy> List<T> find(final PolicyType type) {
+        final Query query = entityManager.createQuery(
+                "SELECT e FROM " + JPAPolicy.class.getSimpleName() + " e WHERE e.type=:type");
+        query.setParameter("type", type);
+
+        return (List<T>) query.getResultList();
+    }
+
+    @Override
+    public List<AccountPolicy> findByResource(final ExternalResource resource) {
+        TypedQuery<AccountPolicy> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAAccountPolicy.class.getSimpleName() + " e "
+                + "WHERE :resource MEMBER OF e.resources", AccountPolicy.class);
+        query.setParameter("resource", resource);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public PasswordPolicy getGlobalPasswordPolicy() {
+        List<? extends Policy> policies = find(PolicyType.GLOBAL_PASSWORD);
+        return policies == null || policies.isEmpty()
+                ? null
+                : (PasswordPolicy) policies.get(0);
+    }
+
+    @Override
+    public AccountPolicy getGlobalAccountPolicy() {
+        List<? extends Policy> policies = find(PolicyType.GLOBAL_ACCOUNT);
+        return policies == null || policies.isEmpty()
+                ? null
+                : (AccountPolicy) policies.get(0);
+    }
+
+    @Override
+    public SyncPolicy getGlobalSyncPolicy() {
+        List<? extends Policy> policies = find(PolicyType.GLOBAL_SYNC);
+        return policies == null || policies.isEmpty()
+                ? null
+                : (SyncPolicy) policies.get(0);
+    }
+
+    @Override
+    public List<Policy> findAll() {
+        TypedQuery<Policy> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAPolicy.class.getSimpleName() + " e", Policy.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public <T extends Policy> T save(final T policy) {
+        switch (policy.getType()) {
+            case GLOBAL_PASSWORD:
+                // just one GLOBAL_PASSWORD policy
+                final PasswordPolicy passwordPolicy = getGlobalPasswordPolicy();
+
+                if (passwordPolicy != null && !passwordPolicy.getKey().equals(policy.getKey())) {
+                    throw new InvalidEntityException(PasswordPolicy.class, EntityViolationType.InvalidPasswordPolicy,
+                            "Global Password policy already exists");
+                }
+                break;
+
+            case GLOBAL_ACCOUNT:
+                // just one GLOBAL_ACCOUNT policy
+                final AccountPolicy accountPolicy = getGlobalAccountPolicy();
+
+                if (accountPolicy != null && !accountPolicy.getKey().equals(policy.getKey())) {
+                    throw new InvalidEntityException(PasswordPolicy.class, EntityViolationType.InvalidAccountPolicy,
+                            "Global Account policy already exists");
+                }
+                break;
+
+            case GLOBAL_SYNC:
+                // just one GLOBAL_SYNC policy
+                final SyncPolicy syncPolicy = getGlobalSyncPolicy();
+
+                if (syncPolicy != null && !syncPolicy.getKey().equals(policy.getKey())) {
+                    throw new InvalidEntityException(PasswordPolicy.class, EntityViolationType.InvalidSyncPolicy,
+                            "Global Synchronization policy already exists");
+                }
+                break;
+
+            case PASSWORD:
+            case ACCOUNT:
+            case SYNC:
+            default:
+        }
+
+        return entityManager.merge(policy);
+    }
+
+    @Override
+    public <T extends Policy> void delete(final T policy) {
+        entityManager.remove(policy);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportDAO.java
new file mode 100644
index 0000000..b398ae1
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportDAO.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.persistence.jpa.dao;
+
+import java.util.Collections;
+import java.util.List;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.persistence.api.dao.ReportDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.jpa.entity.JPAReport;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPAReportDAO extends AbstractDAO<Report, Long> implements ReportDAO {
+
+    @Override
+    @Transactional(readOnly = true)
+    public Report find(final Long key) {
+        return entityManager.find(JPAReport.class, key);
+    }
+
+    @Override
+    public List<Report> findAll() {
+        return findAll(-1, -1, Collections.<OrderByClause>emptyList());
+    }
+
+    @Override
+    public List<Report> findAll(final int page, final int itemsPerPage, final List<OrderByClause> orderByClauses) {
+        final TypedQuery<Report> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAReport.class.getSimpleName() + " e "
+                + toOrderByStatement(Report.class, "e", orderByClauses), Report.class);
+
+        query.setFirstResult(itemsPerPage * (page <= 0
+                ? 0
+                : page - 1));
+
+        if (itemsPerPage > 0) {
+            query.setMaxResults(itemsPerPage);
+        }
+
+        return query.getResultList();
+    }
+
+    @Override
+    public int count() {
+        Query countQuery = entityManager.createNativeQuery("SELECT COUNT(id) FROM " + JPAReport.TABLE);
+        return ((Number) countQuery.getSingleResult()).intValue();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public Report save(final Report report) throws InvalidEntityException {
+        return entityManager.merge(report);
+    }
+
+    @Override
+    public void delete(final Long key) {
+        Report report = find(key);
+        if (report == null) {
+            return;
+        }
+
+        delete(report);
+    }
+
+    @Override
+    public void delete(final Report report) {
+        entityManager.remove(report);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportExecDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportExecDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportExecDAO.java
new file mode 100644
index 0000000..d2ba1dc
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPAReportExecDAO.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.persistence.api.entity.Report;
+import org.apache.syncope.persistence.api.entity.ReportExec;
+import org.apache.syncope.persistence.jpa.entity.JPAReportExec;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPAReportExecDAO extends AbstractDAO<ReportExec, Long> implements ReportExecDAO {
+
+    @Override
+    public ReportExec find(final Long key) {
+        return entityManager.find(JPAReportExec.class, key);
+    }
+
+    private ReportExec findLatest(final Report report, final String field) {
+        TypedQuery<ReportExec> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAReportExec.class.getSimpleName() + " e "
+                + "WHERE e.report=:report ORDER BY e." + field + " DESC", ReportExec.class);
+        query.setParameter("report", report);
+        query.setMaxResults(1);
+
+        List<ReportExec> result = query.getResultList();
+        return result == null || result.isEmpty()
+                ? null
+                : result.iterator().next();
+    }
+
+    @Override
+    public ReportExec findLatestStarted(final Report report) {
+        return findLatest(report, "startDate");
+    }
+
+    @Override
+    public ReportExec findLatestEnded(final Report report) {
+        return findLatest(report, "endDate");
+    }
+
+    @Override
+    public List<ReportExec> findAll() {
+        TypedQuery<ReportExec> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAReportExec.class.getSimpleName() + " e", ReportExec.class);
+        return query.getResultList();
+    }
+
+    /**
+     * This method is annotated as transactional because called from ReportJob.
+     *
+     * @see org.apache.syncope.core.report.ReportJob
+     * @param execution to be merged
+     * @return merged execution
+     * @throws InvalidEntityException if any validation error occurs
+     */
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public ReportExec save(final ReportExec execution) throws InvalidEntityException {
+        return entityManager.merge(execution);
+    }
+
+    @Override
+    public void delete(final Long key) {
+        ReportExec execution = find(key);
+        if (execution == null) {
+            return;
+        }
+
+        delete(execution);
+    }
+
+    @Override
+    public void delete(final ReportExec execution) {
+        if (execution.getReport() != null) {
+            execution.getReport().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/JPARoleDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
new file mode 100644
index 0000000..96174fe
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPARoleDAO.java
@@ -0,0 +1,531 @@
+/*
+ * 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.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.DerAttrDAO;
+import org.apache.syncope.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.entity.AttrTemplate;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.Entitlement;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrValue;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
+import org.apache.syncope.persistence.jpa.entity.membership.JPAMembership;
+import org.apache.syncope.persistence.jpa.entity.role.JPARole;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPARoleDAO extends AbstractSubjectDAO<RPlainAttr, RDerAttr, RVirAttr> implements RoleDAO {
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private PlainAttrDAO plainAttrDAO;
+
+    @Autowired
+    private DerAttrDAO derAttrDAO;
+
+    @Autowired
+    private VirAttrDAO virAttrDAO;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Override
+    protected Subject<RPlainAttr, RDerAttr, RVirAttr> findInternal(final Long key) {
+        return find(key);
+    }
+
+    @Override
+    public Role find(final Long key) {
+        TypedQuery<Role> query = entityManager.createQuery(
+                "SELECT e FROM " + JPARole.class.getSimpleName() + " e WHERE e.id = :id", Role.class);
+        query.setParameter("id", key);
+
+        Role result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No role found with id {}", key, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<Role> find(final String name) {
+        TypedQuery<Role> query = entityManager.createQuery(
+                "SELECT e FROM " + JPARole.class.getSimpleName() + " e WHERE e.name = :name", Role.class);
+        query.setParameter("name", name);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public Role find(final String name, final Long parentId) {
+        TypedQuery<Role> query;
+        if (parentId == null) {
+            query = entityManager.createQuery("SELECT r FROM " + JPARole.class.getSimpleName() + " r WHERE "
+                    + "r.name=:name AND r.parent IS NULL", Role.class);
+        } else {
+            query = entityManager.createQuery("SELECT r FROM " + JPARole.class.getSimpleName() + " r WHERE "
+                    + "r.name=:name AND r.parent.id=:parentId", Role.class);
+            query.setParameter("parentId", parentId);
+        }
+        query.setParameter("name", name);
+
+        List<Role> result = query.getResultList();
+        return result.isEmpty()
+                ? null
+                : result.get(0);
+    }
+
+    private void findSameOwnerDescendants(final List<Role> result, final Role role) {
+        List<Role> children = findChildren(role);
+        if (children != null) {
+            for (Role child : children) {
+                if ((child.getUserOwner() == null && child.getRoleOwner() == null && child.isInheritOwner())
+                        || (child.getUserOwner() != null && child.getUserOwner().equals(role.getUserOwner()))
+                        || (child.getRoleOwner() != null && child.getRoleOwner().equals(role.getRoleOwner()))) {
+
+                    findDescendants(result, child);
+                }
+            }
+        }
+        result.add(role);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<Role> findOwnedByUser(final Long userKey) {
+        User owner = userDAO.find(userKey);
+        if (owner == null) {
+            return Collections.<Role>emptyList();
+        }
+
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").append(JPARole.class.getSimpleName()).
+                append(" e WHERE e.userOwner=:owner ");
+        for (Long roleId : owner.getRoleIds()) {
+            queryString.append("OR e.roleOwner.id=").append(roleId).append(' ');
+        }
+
+        TypedQuery<Role> query = entityManager.createQuery(queryString.toString(), Role.class);
+        query.setParameter("owner", owner);
+
+        List<Role> result = new ArrayList<>();
+        for (Role role : query.getResultList()) {
+            findSameOwnerDescendants(result, role);
+        }
+
+        return result;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<Role> findOwnedByRole(final Long roleId) {
+        Role owner = find(roleId);
+        if (owner == null) {
+            return Collections.<Role>emptyList();
+        }
+
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").append(JPARole.class.getSimpleName()).
+                append(" e WHERE e.roleOwner=:owner ");
+
+        TypedQuery<Role> query = entityManager.createQuery(queryString.toString(), Role.class);
+        query.setParameter("owner", owner);
+
+        List<Role> result = new ArrayList<Role>();
+        for (Role role : query.getResultList()) {
+            findSameOwnerDescendants(result, role);
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<Role> findByEntitlement(final Entitlement entitlement) {
+        TypedQuery<Role> query = entityManager.createQuery(
+                "SELECT e FROM " + JPARole.class.getSimpleName() + " e "
+                + "WHERE :entitlement MEMBER OF e.entitlements", Role.class);
+        query.setParameter("entitlement", entitlement);
+
+        return query.getResultList();
+    }
+
+    private Map.Entry<String, String> getPolicyFields(final PolicyType type) {
+        String policyField;
+        String inheritPolicyField;
+        if (type == PolicyType.GLOBAL_ACCOUNT || type == PolicyType.ACCOUNT) {
+            policyField = "accountPolicy";
+            inheritPolicyField = "inheritAccountPolicy";
+        } else {
+            policyField = "passwordPolicy";
+            inheritPolicyField = "inheritPasswordPolicy";
+        }
+
+        return new AbstractMap.SimpleEntry<>(policyField, inheritPolicyField);
+    }
+
+    private List<Role> findSamePolicyChildren(final Role role, final PolicyType type) {
+        List<Role> result = new ArrayList<Role>();
+
+        for (Role child : findChildren(role)) {
+            boolean inherit = type == PolicyType.GLOBAL_ACCOUNT || type == PolicyType.ACCOUNT
+                    ? child.isInheritAccountPolicy()
+                    : child.isInheritPasswordPolicy();
+            if (inherit) {
+                result.add(child);
+                result.addAll(findSamePolicyChildren(child, type));
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<Role> findByPolicy(final Policy policy) {
+        if (policy.getType() == PolicyType.GLOBAL_SYNC || policy.getType() == PolicyType.SYNC) {
+            return Collections.<Role>emptyList();
+        }
+
+        Map.Entry<String, String> policyFields = getPolicyFields(policy.getType());
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+                append(JPARole.class.getSimpleName()).append(" e WHERE e.").
+                append(policyFields.getKey()).append(" = :policy AND (e.").
+                append(policyFields.getValue()).append(" IS NULL OR e.").
+                append(policyFields.getValue()).append(" = 0)");
+
+        TypedQuery<Role> query = entityManager.createQuery(queryString.toString(), Role.class);
+        query.setParameter("policy", policy);
+
+        List<Role> result = new ArrayList<Role>();
+        for (Role role : query.getResultList()) {
+            result.add(role);
+            result.addAll(findSamePolicyChildren(role, policy.getType()));
+        }
+        return result;
+    }
+
+    @Override
+    public List<Role> findWithoutPolicy(final PolicyType type) {
+        if (type == PolicyType.GLOBAL_SYNC || type == PolicyType.SYNC) {
+            return Collections.<Role>emptyList();
+        }
+
+        Map.Entry<String, String> policyFields = getPolicyFields(type);
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+                append(JPARole.class.getSimpleName()).append(" e WHERE e.").
+                append(policyFields.getKey()).append(" IS NULL AND (e.").
+                append(policyFields.getValue()).append(" IS NULL OR e.").
+                append(policyFields.getValue()).append(" = 0)");
+
+        TypedQuery<Role> query = entityManager.createQuery(queryString.toString(), Role.class);
+        return query.getResultList();
+    }
+
+    private void findAncestors(final List<Role> result, final Role role) {
+        if (role.getParent() != null && !result.contains(role.getParent())) {
+            result.add(role.getParent());
+            findAncestors(result, role.getParent());
+        }
+    }
+
+    @Override
+    public List<Role> findAncestors(final Role role) {
+        List<Role> result = new ArrayList<>();
+        findAncestors(result, role);
+        return result;
+    }
+
+    @Override
+    public List<Role> findChildren(final Role role) {
+        TypedQuery<Role> query = entityManager.createQuery(
+                "SELECT r FROM " + JPARole.class.getSimpleName() + " r WHERE r.parent=:role", Role.class);
+        query.setParameter("role", role);
+
+        return query.getResultList();
+    }
+
+    private void findDescendants(final List<Role> result, final Role role) {
+        List<Role> children = findChildren(role);
+        if (children != null) {
+            for (Role child : children) {
+                findDescendants(result, child);
+            }
+        }
+        result.add(role);
+    }
+
+    @Override
+    public List<Role> findDescendants(final Role role) {
+        List<Role> result = new ArrayList<>();
+        findDescendants(result, role);
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<Role> findByAttrValue(final String schemaName, final RPlainAttrValue attrValue) {
+        return (List<Role>) findByAttrValue(
+                schemaName, attrValue, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Role findByAttrUniqueValue(final String schemaName, final RPlainAttrValue attrUniqueValue) {
+        return (Role) findByAttrUniqueValue(schemaName, attrUniqueValue,
+                JPAAttributableUtil.getInstance(AttributableType.ROLE));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<Role> findByDerAttrValue(final String schemaName, final String value) {
+        return (List<Role>) findByDerAttrValue(
+                schemaName, value, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<Role> findByResource(final ExternalResource resource) {
+        return (List<Role>) findByResource(resource, JPAAttributableUtil.getInstance(AttributableType.ROLE));
+    }
+
+    @Override
+    public List<Role> findAll() {
+        return findAll(-1, -1, Collections.<OrderByClause>emptyList());
+    }
+
+    @Override
+    public List<Role> findAll(final int page, final int itemsPerPage, final List<OrderByClause> orderBy) {
+        TypedQuery<Role> query = entityManager.createQuery(
+                "SELECT e FROM " + JPARole.class.getSimpleName() + " e "
+                + toOrderByStatement(Role.class, "e", orderBy), Role.class);
+
+        query.setFirstResult(itemsPerPage * (page <= 0
+                ? 0
+                : page - 1));
+
+        if (itemsPerPage > 0) {
+            query.setMaxResults(itemsPerPage);
+        }
+
+        return query.getResultList();
+    }
+
+    @Override
+    public List<Membership> findMemberships(final Role role) {
+        TypedQuery<Membership> query = entityManager.createQuery(
+                "SELECT e FROM " + JPAMembership.class.getSimpleName() + " e"
+                + " WHERE e.role=:role", Membership.class);
+        query.setParameter("role", role);
+
+        return query.getResultList();
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Long> unmatched(final Long roleId,
+            final Class<?> attrClass, final Class<? extends AttrTemplate<?>> attrTemplateClass) {
+
+        final Query query = entityManager.createNativeQuery(new StringBuilder().
+                append("SELECT ma.id ").
+                append("FROM ").append(JPAMembership.TABLE).append(" m, ").
+                append(attrClass.getSimpleName()).append(" ma ").
+                append("WHERE m.role_id = ?1 ").
+                append("AND ma.owner_id = m.id ").
+                append("AND ma.template_id NOT IN (").
+                append("SELECT id ").
+                append("FROM ").append(attrTemplateClass.getSimpleName()).append(' ').
+                append("WHERE owner_id = ?1)").toString());
+        query.setParameter(1, roleId);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public final int count() {
+        Query countQuery = entityManager.createNativeQuery(
+                "SELECT COUNT(e.id) FROM " + JPARole.TABLE + " e");
+
+        return ((Number) countQuery.getSingleResult()).intValue();
+    }
+
+    @Override
+    public Role save(final Role role) {
+        // reset account policy in case of inheritance
+        if (role.isInheritAccountPolicy()) {
+            role.setAccountPolicy(null);
+        }
+
+        // reset password policy in case of inheritance
+        if (role.isInheritPasswordPolicy()) {
+            role.setPasswordPolicy(null);
+        }
+
+        // remove attributes without a valid template
+        List<RPlainAttr> rToBeDeleted = new ArrayList<>();
+        for (PlainAttr attr : role.getPlainAttrs()) {
+            boolean found = false;
+            for (RPlainAttrTemplate template : role.findInheritedTemplates(RPlainAttrTemplate.class)) {
+                if (template.getSchema().equals(attr.getSchema())) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                rToBeDeleted.add((RPlainAttr) attr);
+            }
+        }
+        for (RPlainAttr attr : rToBeDeleted) {
+            LOG.debug("Removing {} from {} because no template is available for it", attr, role);
+            role.removePlainAttr(attr);
+        }
+
+        // remove derived attributes without a valid template
+        List<RDerAttr> rDerToBeDeleted = new ArrayList<RDerAttr>();
+        for (DerAttr attr : role.getDerAttrs()) {
+            boolean found = false;
+            for (RDerAttrTemplate template : role.findInheritedTemplates(RDerAttrTemplate.class)) {
+                if (template.getSchema().equals(attr.getSchema())) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                rDerToBeDeleted.add((RDerAttr) attr);
+            }
+        }
+        for (RDerAttr attr : rDerToBeDeleted) {
+            LOG.debug("Removing {} from {} because no template is available for it", attr, role);
+            role.removeDerAttr(attr);
+        }
+
+        // remove virtual attributes without a valid template
+        List<RVirAttr> rVirToBeDeleted = new ArrayList<RVirAttr>();
+        for (VirAttr attr : role.getVirAttrs()) {
+            boolean found = false;
+            for (RVirAttrTemplate template : role.findInheritedTemplates(RVirAttrTemplate.class)) {
+                if (template.getSchema().equals(attr.getSchema())) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                LOG.debug("Removing {} from {} because no template is available for it", attr, role);
+                rVirToBeDeleted.add((RVirAttr) attr);
+            }
+        }
+        for (RVirAttr attr : rVirToBeDeleted) {
+            role.removeVirAttr(attr);
+        }
+
+        Role merged = entityManager.merge(role);
+
+        // Now the same process for any exising membership of the role being saved
+        if (role.getKey() != null) {
+            for (Long key : unmatched(role.getKey(), MPlainAttr.class, MPlainAttrTemplate.class)) {
+                LOG.debug("Removing MAttr[{}] because no template is available for it in {}", key, role);
+                plainAttrDAO.delete(key, MPlainAttr.class);
+            }
+            for (Long id : unmatched(role.getKey(), MDerAttr.class, MDerAttrTemplate.class)) {
+                LOG.debug("Removing MDerAttr[{}] because no template is available for it in {}", id, role);
+                derAttrDAO.delete(id, MDerAttr.class);
+            }
+            for (Long id : unmatched(role.getKey(), MVirAttr.class, MVirAttrTemplate.class)) {
+                LOG.debug("Removing MVirAttr[{}] because no template is available for it in {}", id, role);
+                virAttrDAO.delete(id, MVirAttr.class);
+            }
+        }
+
+        merged = entityManager.merge(merged);
+        for (VirAttr attr : merged.getVirAttrs()) {
+            attr.getValues().clear();
+            attr.getValues().addAll(role.getVirAttr(attr.getSchema().getKey()).getValues());
+        }
+
+        entitlementDAO.saveRoleEntitlement(merged);
+
+        return merged;
+    }
+
+    @Override
+    public void delete(final Role role) {
+        for (Role roleToBeDeleted : findDescendants(role)) {
+            for (Membership membership : findMemberships(roleToBeDeleted)) {
+                membership.getUser().removeMembership(membership);
+                userDAO.save(membership.getUser());
+
+                entityManager.remove(membership);
+            }
+
+            roleToBeDeleted.getEntitlements().clear();
+
+            roleToBeDeleted.setParent(null);
+            roleToBeDeleted.setUserOwner(null);
+            roleToBeDeleted.setRoleOwner(null);
+            entityManager.remove(roleToBeDeleted);
+
+            entitlementDAO.delete(RoleEntitlementUtil.getEntitlementNameFromRoleId(roleToBeDeleted.getKey()));
+        }
+    }
+
+    @Override
+    public void delete(final Long key) {
+        Role role = (Role) findInternal(key);
+        if (role == null) {
+            return;
+        }
+
+        delete(role);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASecurityQuestionDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASecurityQuestionDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASecurityQuestionDAO.java
new file mode 100644
index 0000000..f17e7c0
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASecurityQuestionDAO.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.persistence.api.dao.SecurityQuestionDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.persistence.jpa.entity.JPASecurityQuestion;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPASecurityQuestionDAO extends AbstractDAO<SecurityQuestion, Long> implements SecurityQuestionDAO {
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Override
+    public SecurityQuestion find(final Long key) {
+        return entityManager.find(JPASecurityQuestion.class, key);
+    }
+
+    @Override
+    public List<SecurityQuestion> findAll() {
+        final TypedQuery<SecurityQuestion> query = entityManager.createQuery(
+                "SELECT e FROM " + JPASecurityQuestion.class.getSimpleName() + " e ", SecurityQuestion.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public SecurityQuestion save(final SecurityQuestion securityQuestion) throws InvalidEntityException {
+        return entityManager.merge(securityQuestion);
+    }
+
+    @Override
+    public void delete(final Long key) {
+        SecurityQuestion securityQuestion = find(key);
+        if (securityQuestion == null) {
+            return;
+        }
+
+        for (User user : userDAO.findBySecurityQuestion(securityQuestion)) {
+            user.setSecurityQuestion(null);
+            user.setSecurityAnswer(null);
+            userDAO.save(user);
+        }
+
+        entityManager.remove(securityQuestion);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
----------------------------------------------------------------------
diff --git a/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
new file mode 100644
index 0000000..86e596f
--- /dev/null
+++ b/syncope620/server/persistence-jpa/src/main/java/org/apache/syncope/persistence/jpa/dao/JPASubjectSearchDAO.java
@@ -0,0 +1,715 @@
+/*
+ * 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.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.persistence.api.dao.PlainSchemaDAO;
+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.EntitlementCond;
+import org.apache.syncope.persistence.api.dao.search.MembershipCond;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.dao.search.SubjectCond;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.jpa.entity.JPAAttributableUtil;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+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;
+
+    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 = JPAAttributableUtil.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 = JPAAttributableUtil.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 = JPAAttributableUtil.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.inheritAttrs, 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();
+    }
+}