http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java new file mode 100644 index 0000000..094840d --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.dao.PolicyDAO; +import org.apache.syncope.core.persistence.api.entity.AccountPolicy; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.PasswordPolicy; +import org.apache.syncope.core.persistence.api.entity.Policy; +import org.apache.syncope.core.persistence.api.entity.SyncPolicy; +import org.apache.syncope.core.persistence.jpa.entity.JPAPolicy; +import org.apache.syncope.core.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/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportDAO.java new file mode 100644 index 0000000..c60ff38 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.persistence.jpa.dao; + +import java.util.Collections; +import java.util.List; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import org.apache.syncope.core.persistence.api.dao.ReportDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.entity.Report; +import org.apache.syncope.core.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/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportExecDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportExecDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAReportExecDAO.java new file mode 100644 index 0000000..d6c1f97 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.persistence.jpa.dao; + +import java.util.List; +import javax.persistence.TypedQuery; +import org.apache.syncope.core.persistence.api.dao.ReportExecDAO; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.entity.Report; +import org.apache.syncope.core.persistence.api.entity.ReportExec; +import org.apache.syncope.core.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/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java new file mode 100644 index 0000000..3003bb4 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java @@ -0,0 +1,584 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +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.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.RoleEntitlementUtil; +import org.apache.syncope.core.persistence.api.dao.DerAttrDAO; +import org.apache.syncope.core.persistence.api.dao.EntitlementDAO; +import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.VirAttrDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.AttrTemplate; +import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory; +import org.apache.syncope.core.persistence.api.entity.DerAttr; +import org.apache.syncope.core.persistence.api.entity.Entitlement; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Policy; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.membership.MDerAttr; +import org.apache.syncope.core.persistence.api.entity.membership.MDerAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.membership.MPlainAttr; +import org.apache.syncope.core.persistence.api.entity.membership.MPlainAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.membership.MVirAttr; +import org.apache.syncope.core.persistence.api.entity.membership.MVirAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.role.RDerAttr; +import org.apache.syncope.core.persistence.api.entity.role.RDerAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.role.RPlainAttr; +import org.apache.syncope.core.persistence.api.entity.role.RPlainAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.role.RPlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.role.RVirAttr; +import org.apache.syncope.core.persistence.api.entity.role.RVirAttrTemplate; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.persistence.jpa.entity.membership.JPAMembership; +import org.apache.syncope.core.persistence.jpa.entity.role.JPARole; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.misc.security.AuthContextUtil; +import org.apache.syncope.core.misc.security.UnauthorizedRoleException; +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; + + @Autowired + private AttributableUtilFactory attrUtilFactory; + + @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 roleKey : owner.getRoleKeys()) { + queryString.append("OR e.roleOwner.id=").append(roleKey).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, attrUtilFactory.getInstance(AttributableType.ROLE)); + } + + @SuppressWarnings("unchecked") + @Override + public Role findByAttrUniqueValue(final String schemaName, final RPlainAttrValue attrUniqueValue) { + return (Role) findByAttrUniqueValue(schemaName, attrUniqueValue, + attrUtilFactory.getInstance(AttributableType.ROLE)); + } + + @SuppressWarnings("unchecked") + @Override + public List<Role> findByDerAttrValue(final String schemaName, final String value) { + return (List<Role>) findByDerAttrValue( + schemaName, value, attrUtilFactory.getInstance(AttributableType.ROLE)); + } + + @SuppressWarnings("unchecked") + @Override + public List<Role> findByResource(final ExternalResource resource) { + return (List<Role>) findByResource(resource, attrUtilFactory.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.getEntitlementNameFromRoleKey(roleToBeDeleted.getKey())); + } + } + + @Override + public void delete(final Long key) { + Role role = (Role) findInternal(key); + if (role == null) { + return; + } + + delete(role); + } + + @Override + public Role authFetch(Long key) { + if (key == null) { + throw new NotFoundException("Null role id"); + } + + Role role = find(key); + if (role == null) { + throw new NotFoundException("Role " + key); + } + + Set<Long> allowedRoleKeys = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()); + if (!allowedRoleKeys.contains(role.getKey())) { + throw new UnauthorizedRoleException(role.getKey()); + } + return role; + } + + @Transactional(readOnly = true) + @Override + public Map<Long, PropagationByResource> findUsersWithIndirectResources(final Long roleKey) { + Role role = authFetch(roleKey); + + Map<Long, PropagationByResource> result = new HashMap<>(); + + for (Membership membership : findMemberships(role)) { + User user = membership.getUser(); + + PropagationByResource propByRes = new PropagationByResource(); + for (ExternalResource resource : role.getResources()) { + if (!user.getOwnResources().contains(resource)) { + propByRes.add(ResourceOperation.DELETE, resource.getKey()); + } + + if (!propByRes.isEmpty()) { + result.put(user.getKey(), propByRes); + } + } + } + + return result; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASecurityQuestionDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASecurityQuestionDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASecurityQuestionDAO.java new file mode 100644 index 0000000..b002798 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.persistence.jpa.dao; + +import java.util.List; +import javax.persistence.TypedQuery; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.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/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASubjectSearchDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASubjectSearchDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASubjectSearchDAO.java new file mode 100644 index 0000000..0fec6de --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASubjectSearchDAO.java @@ -0,0 +1,736 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +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.ClassUtils; +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.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.AttributeCond; +import org.apache.syncope.core.persistence.api.dao.search.EntitlementCond; +import org.apache.syncope.core.persistence.api.dao.search.MembershipCond; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.dao.search.ResourceCond; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.apache.syncope.core.persistence.api.dao.search.SubjectCond; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory; +import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import org.springframework.util.ReflectionUtils; + +@Repository +public class JPASubjectSearchDAO extends AbstractDAO<Subject<?, ?, ?>, Long> implements SubjectSearchDAO { + + private static final String EMPTY_ATTR_QUERY = "SELECT subject_id FROM user_search_attr WHERE 1=2"; + + @Autowired + private UserDAO userDAO; + + @Autowired + private RoleDAO roleDAO; + + @Autowired + private PlainSchemaDAO schemaDAO; + + @Autowired + private AttributableUtilFactory attrUtilFactory; + + private String getAdminRolesFilter(final Set<Long> adminRoles, final SubjectType type) { + final StringBuilder adminRolesFilter = new StringBuilder(); + + if (type == SubjectType.USER) { + adminRolesFilter.append("SELECT user_id AS subject_id FROM Membership M1 WHERE role_id IN ("). + append("SELECT role_id FROM Membership M2 WHERE M2.user_id=M1.user_id "). + append("AND role_id NOT IN ("); + } + + adminRolesFilter.append("SELECT id AS "). + append(type == SubjectType.USER ? "role" : "subject"). + append("_id FROM SyncopeRole"); + + boolean firstRole = true; + + for (Long adminRoleId : adminRoles) { + if (firstRole) { + adminRolesFilter.append(" WHERE"); + firstRole = false; + } else { + adminRolesFilter.append(type == SubjectType.USER ? " OR" : " AND"); + } + adminRolesFilter.append(type == SubjectType.USER ? " id = " : " id <> ").append(adminRoleId); + } + + if (type == SubjectType.USER) { + adminRolesFilter.append("))"); + } + + return adminRolesFilter.toString(); + } + + @Override + public int count(final Set<Long> adminRoles, final SearchCond searchCondition, final SubjectType type) { + List<Object> parameters = Collections.synchronizedList(new ArrayList<>()); + + // 1. get the query string from the search condition + SearchSupport svs = new SearchSupport(type); + StringBuilder queryString = getQuery(searchCondition, parameters, type, svs); + + // 2. take into account administrative roles + queryString.insert(0, "SELECT u.subject_id FROM ("); + queryString.append(") u WHERE subject_id NOT IN ("); + queryString.append(getAdminRolesFilter(adminRoles, type)).append(')'); + + // 3. prepare the COUNT query + queryString.insert(0, "SELECT COUNT(subject_id) FROM ("); + queryString.append(") count_subject_id"); + + Query countQuery = entityManager.createNativeQuery(queryString.toString()); + fillWithParameters(countQuery, parameters); + + LOG.debug("Native count query\n{}\nwith parameters\n{}", queryString.toString(), parameters); + + int result = ((Number) countQuery.getSingleResult()).intValue(); + LOG.debug("Native count query result: {}", result); + + return result; + } + + @Override + public <T extends Subject<?, ?, ?>> List<T> search( + final Set<Long> adminRoles, final SearchCond searchCondition, final SubjectType type) { + + return search(adminRoles, searchCondition, Collections.<OrderByClause>emptyList(), type); + } + + @Override + public <T extends Subject<?, ?, ?>> List<T> search( + final Set<Long> adminRoles, final SearchCond searchCondition, final List<OrderByClause> orderBy, + final SubjectType type) { + + return search(adminRoles, searchCondition, -1, -1, orderBy, type); + } + + @Override + public <T extends Subject<?, ?, ?>> List<T> search( + final Set<Long> adminRoles, final SearchCond searchCondition, final int page, final int itemsPerPage, + final List<OrderByClause> orderBy, final SubjectType type) { + + List<T> result = Collections.<T>emptyList(); + + if (adminRoles != null && (!adminRoles.isEmpty() || roleDAO.findAll().isEmpty())) { + LOG.debug("Search condition:\n{}", searchCondition); + + if (searchCondition != null && searchCondition.isValid()) { + try { + result = doSearch(adminRoles, searchCondition, page, itemsPerPage, orderBy, type); + } catch (Exception e) { + LOG.error("While searching for {}", type, e); + } + } else { + LOG.error("Invalid search condition:\n{}", searchCondition); + } + } + + return result; + } + + @Override + public <T extends Subject<?, ?, ?>> boolean matches( + final T subject, final SearchCond searchCondition, final SubjectType type) { + + List<Object> parameters = Collections.synchronizedList(new ArrayList<>()); + + // 1. get the query string from the search condition + SearchSupport svs = new SearchSupport(type); + StringBuilder queryString = getQuery(searchCondition, parameters, type, svs); + + boolean matches; + if (queryString.length() == 0) { + // Could be empty: got into a role search with a single membership condition ... + matches = false; + } else { + // 2. take into account the passed user + queryString.insert(0, "SELECT u.subject_id FROM ("); + queryString.append(") u WHERE subject_id=?").append(setParameter(parameters, subject.getKey())); + + // 3. prepare the search query + Query query = entityManager.createNativeQuery(queryString.toString()); + + // 4. populate the search query with parameter values + fillWithParameters(query, parameters); + + // 5. executes query + matches = !query.getResultList().isEmpty(); + } + + return matches; + } + + private int setParameter(final List<Object> parameters, final Object parameter) { + int key; + synchronized (parameters) { + parameters.add(parameter); + key = parameters.size(); + } + + return key; + } + + private void fillWithParameters(final Query query, final List<Object> parameters) { + for (int i = 0; i < parameters.size(); i++) { + if (parameters.get(i) instanceof Date) { + query.setParameter(i + 1, (Date) parameters.get(i), TemporalType.TIMESTAMP); + } else if (parameters.get(i) instanceof Boolean) { + query.setParameter(i + 1, ((Boolean) parameters.get(i)) + ? 1 + : 0); + } else { + query.setParameter(i + 1, parameters.get(i)); + } + } + } + + private StringBuilder buildSelect(final OrderBySupport orderBySupport) { + final StringBuilder select = new StringBuilder("SELECT u.subject_id"); + + for (OrderBySupport.Item obs : orderBySupport.items) { + select.append(',').append(obs.select); + } + select.append(" FROM "); + + return select; + } + + private StringBuilder buildWhere(final OrderBySupport orderBySupport, final SubjectType type) { + final StringBuilder where = new StringBuilder(" u"); + for (SearchSupport.SearchView searchView : orderBySupport.views) { + where.append(',').append(searchView.name).append(' ').append(searchView.alias); + } + where.append(" WHERE "); + for (SearchSupport.SearchView searchView : orderBySupport.views) { + where.append("u.subject_id=").append(searchView.alias).append(".subject_id AND "); + } + + for (OrderBySupport.Item obs : orderBySupport.items) { + if (StringUtils.isNotBlank(obs.where)) { + where.append(obs.where).append(" AND "); + } + } + where.append("u.subject_id NOT IN ("); + + return where; + } + + private StringBuilder buildOrderBy(final OrderBySupport orderBySupport) { + final StringBuilder orderBy = new StringBuilder(); + + for (OrderBySupport.Item obs : orderBySupport.items) { + orderBy.append(obs.orderBy).append(','); + } + if (!orderBySupport.items.isEmpty()) { + orderBy.insert(0, " ORDER BY "); + orderBy.deleteCharAt(orderBy.length() - 1); + } + + return orderBy; + } + + private OrderBySupport parseOrderBy(final SubjectType type, final SearchSupport svs, + final List<OrderByClause> orderByClauses) { + + final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType()); + + OrderBySupport orderBySupport = new OrderBySupport(); + + for (OrderByClause clause : orderByClauses) { + OrderBySupport.Item obs = new OrderBySupport.Item(); + + // Manage difference among external key attribute and internal JPA @Id + String fieldName = "key".equals(clause.getField()) ? "id" : clause.getField(); + + Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), fieldName); + if (subjectField == null) { + PlainSchema schema = schemaDAO.find(fieldName, 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(fieldName).toString(); + obs.where = new StringBuilder(). + append(svs.uniqueAttr().alias). + append(".schema_name='").append(fieldName).append("'").toString(); + obs.orderBy = fieldName + " " + 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(fieldName).toString(); + obs.where = new StringBuilder(). + append(svs.attr().alias). + append(".schema_name='").append(fieldName).append("'").toString(); + obs.orderBy = fieldName + " " + clause.getDirection().name(); + } + } + } else { + orderBySupport.views.add(svs.field()); + + obs.select = svs.field().alias + "." + fieldName; + obs.where = StringUtils.EMPTY; + obs.orderBy = svs.field().alias + "." + fieldName + " " + clause.getDirection().name(); + } + + if (obs.isEmpty()) { + LOG.warn("Cannot build any valid clause from {}", clause); + } else { + orderBySupport.items.add(obs); + } + } + + return orderBySupport; + } + + @SuppressWarnings("unchecked") + private <T extends Subject<?, ?, ?>> List<T> doSearch(final Set<Long> adminRoles, + final SearchCond nodeCond, final int page, final int itemsPerPage, final List<OrderByClause> orderBy, + final SubjectType type) { + + List<Object> parameters = Collections.synchronizedList(new ArrayList<>()); + + // 1. get the query string from the search condition + SearchSupport svs = new SearchSupport(type); + StringBuilder queryString = getQuery(nodeCond, parameters, type, svs); + + // 2. take into account administrative roles and ordering + OrderBySupport orderBySupport = parseOrderBy(type, svs, orderBy); + if (queryString.charAt(0) == '(') { + queryString.insert(0, buildSelect(orderBySupport)); + queryString.append(buildWhere(orderBySupport, type)); + } else { + queryString.insert(0, buildSelect(orderBySupport).append('(')); + queryString.append(')').append(buildWhere(orderBySupport, type)); + } + queryString. + append(getAdminRolesFilter(adminRoles, type)).append(')'). + append(buildOrderBy(orderBySupport)); + + // 3. prepare the search query + Query query = entityManager.createNativeQuery(queryString.toString()); + + // 4. page starts from 1, while setFirtResult() starts from 0 + query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1)); + + if (itemsPerPage >= 0) { + query.setMaxResults(itemsPerPage); + } + + // 5. populate the search query with parameter values + fillWithParameters(query, parameters); + + LOG.debug("Native query\n{}\nwith parameters\n{}", queryString.toString(), parameters); + + // 6. Prepare the result (avoiding duplicates) + List<T> result = new ArrayList<>(); + + for (Object subjectId : query.getResultList()) { + long actualId; + if (subjectId instanceof Object[]) { + actualId = ((Number) ((Object[]) subjectId)[0]).longValue(); + } else { + actualId = ((Number) subjectId).longValue(); + } + + T subject = type == SubjectType.USER + ? (T) userDAO.find(actualId) + : (T) roleDAO.find(actualId); + if (subject == null) { + LOG.error("Could not find {} with id {}, even though returned by the native query", + type, actualId); + } else { + if (!result.contains(subject)) { + result.add(subject); + } + } + } + + return result; + } + + private StringBuilder getQuery(final SearchCond nodeCond, final List<Object> parameters, + final SubjectType type, final SearchSupport svs) { + + StringBuilder query = new StringBuilder(); + + switch (nodeCond.getType()) { + + case LEAF: + case NOT_LEAF: + if (nodeCond.getMembershipCond() != null && SubjectType.USER == type) { + query.append(getQuery(nodeCond.getMembershipCond(), + nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs)); + } + if (nodeCond.getResourceCond() != null) { + query.append(getQuery(nodeCond.getResourceCond(), + nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs)); + } + if (nodeCond.getEntitlementCond() != null) { + query.append(getQuery(nodeCond.getEntitlementCond(), + nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, svs)); + } + if (nodeCond.getAttributeCond() != null) { + query.append(getQuery(nodeCond.getAttributeCond(), + nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs)); + } + if (nodeCond.getSubjectCond() != null) { + query.append(getQuery(nodeCond.getSubjectCond(), + nodeCond.getType() == SearchCond.Type.NOT_LEAF, parameters, type, svs)); + } + break; + + case AND: + query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, type, svs)). + append(" AND subject_id IN ( "). + append(getQuery(nodeCond.getRightNodeCond(), parameters, type, svs)). + append(")"); + break; + + case OR: + query.append(getQuery(nodeCond.getLeftNodeCond(), parameters, type, svs)). + append(" OR subject_id IN ( "). + append(getQuery(nodeCond.getRightNodeCond(), parameters, type, svs)). + append(")"); + break; + + default: + } + + return query; + } + + private String getQuery(final MembershipCond cond, final boolean not, final List<Object> parameters, + final SearchSupport svs) { + + StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM "). + append(svs.field().name).append(" WHERE "); + + if (not) { + query.append("subject_id NOT IN ("); + } else { + query.append("subject_id IN ("); + } + + query.append("SELECT DISTINCT subject_id ").append("FROM "). + append(svs.membership().name).append(" WHERE "). + append("role_id=?").append(setParameter(parameters, cond.getRoleId())). + append(')'); + + return query.toString(); + } + + private String getQuery(final ResourceCond cond, final boolean not, final List<Object> parameters, + final SubjectType type, final SearchSupport svs) { + + final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM "). + append(svs.field().name).append(" WHERE "); + + if (not) { + query.append("subject_id NOT IN ("); + } else { + query.append("subject_id IN ("); + } + + query.append("SELECT DISTINCT subject_id FROM "). + append(svs.resource().name). + append(" WHERE resource_name=?"). + append(setParameter(parameters, cond.getResourceName())); + + if (type == SubjectType.USER) { + query.append(" UNION SELECT DISTINCT subject_id FROM "). + append(svs.roleResource().name). + append(" WHERE resource_name=?"). + append(setParameter(parameters, cond.getResourceName())); + } + + query.append(')'); + + return query.toString(); + } + + private String getQuery(final EntitlementCond cond, final boolean not, final List<Object> parameters, + final SearchSupport svs) { + + final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM "). + append(svs.entitlements().name). + append(" WHERE entitlement_name "); + if (not) { + query.append(" NOT "); + } + query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression())); + + return query.toString(); + } + + private void fillAttributeQuery(final StringBuilder query, final PlainAttrValue attrValue, + final PlainSchema schema, final AttributeCond cond, final boolean not, + final List<Object> parameters, final SearchSupport svs) { + + String column = (cond instanceof SubjectCond) + ? cond.getSchema() + : "' AND " + svs.fieldName(schema.getType()); + + switch (cond.getType()) { + + case ISNULL: + query.append(column).append(not + ? " IS NOT NULL" + : " IS NULL"); + break; + + case ISNOTNULL: + query.append(column).append(not + ? " IS NULL" + : " IS NOT NULL"); + break; + + case LIKE: + if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) { + query.append(column); + if (not) { + query.append(" NOT "); + } + query.append(" LIKE ?").append(setParameter(parameters, cond.getExpression())); + } else { + if (!(cond instanceof SubjectCond)) { + query.append("' AND"); + } + query.append(" 1=2"); + LOG.error("LIKE is only compatible with string or enum schemas"); + } + break; + + case EQ: + query.append(column); + if (not) { + query.append("<>"); + } else { + query.append('='); + } + query.append('?').append(setParameter(parameters, attrValue.getValue())); + break; + + case GE: + query.append(column); + if (not) { + query.append('<'); + } else { + query.append(">="); + } + query.append('?').append(setParameter(parameters, attrValue.getValue())); + break; + + case GT: + query.append(column); + if (not) { + query.append("<="); + } else { + query.append('>'); + } + query.append('?').append(setParameter(parameters, attrValue.getValue())); + break; + + case LE: + query.append(column); + if (not) { + query.append('>'); + } else { + query.append("<="); + } + query.append('?').append(setParameter(parameters, attrValue.getValue())); + break; + + case LT: + query.append(column); + if (not) { + query.append(">="); + } else { + query.append('<'); + } + query.append('?').append(setParameter(parameters, attrValue.getValue())); + break; + + default: + } + } + + private String getQuery(final AttributeCond cond, final boolean not, final List<Object> parameters, + final SubjectType type, final SearchSupport svs) { + + final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType()); + + PlainSchema schema = schemaDAO.find(cond.getSchema(), attrUtil.plainSchemaClass()); + if (schema == null) { + LOG.warn("Ignoring invalid schema '{}'", cond.getSchema()); + return EMPTY_ATTR_QUERY; + } + + PlainAttrValue attrValue = attrUtil.newPlainAttrValue(); + try { + if (cond.getType() != AttributeCond.Type.LIKE && cond.getType() != AttributeCond.Type.ISNULL + && cond.getType() != AttributeCond.Type.ISNOTNULL) { + + schema.getValidator().validate(cond.getExpression(), attrValue); + } + } catch (ValidationException e) { + LOG.error("Could not validate expression '" + cond.getExpression() + "'", e); + return EMPTY_ATTR_QUERY; + } + + StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM "); + if (cond.getType() == AttributeCond.Type.ISNOTNULL) { + query.append(svs.field().name). + append(" WHERE subject_id NOT IN (SELECT subject_id FROM "). + append(svs.nullAttr().name). + append(" WHERE schema_name='").append(schema.getKey()).append("')"); + } else { + if (cond.getType() == AttributeCond.Type.ISNULL) { + query.append(svs.nullAttr().name). + append(" WHERE schema_name='").append(schema.getKey()).append("'"); + } else { + if (schema.isUniqueConstraint()) { + query.append(svs.uniqueAttr().name); + } else { + query.append(svs.attr().name); + } + query.append(" WHERE schema_name='").append(schema.getKey()); + + fillAttributeQuery(query, attrValue, schema, cond, not, parameters, svs); + } + } + + return query.toString(); + } + + @SuppressWarnings("rawtypes") + private String getQuery(final SubjectCond cond, final boolean not, final List<Object> parameters, + final SubjectType type, final SearchSupport svs) { + + final AttributableUtil attrUtil = attrUtilFactory.getInstance(type.asAttributableType()); + + // Keeps track of difference between entity's getKey() and JPA @Id fields + if ("key".equals(cond.getSchema())) { + cond.setSchema("id"); + } + + 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 + // (JPARole.inheritPlainAttrs, for example) + boolean foundBooleanMin = false; + boolean foundBooleanMax = false; + if (Integer.class.equals(subjectField.getType())) { + for (Annotation annotation : subjectField.getAnnotations()) { + if (Min.class.equals(annotation.annotationType())) { + foundBooleanMin = ((Min) annotation).value() == 0; + } else if (Max.class.equals(annotation.annotationType())) { + foundBooleanMax = ((Max) annotation).value() == 1; + } + } + } + if (foundBooleanMin && foundBooleanMax) { + schema.setType(AttrSchemaType.Boolean); + } + + // Deal with subject fields representing relationships to other entities + if (subjectField.getType().getAnnotation(Entity.class) != null) { + Method relMethod = null; + try { + relMethod = ClassUtils.getPublicMethod(subjectField.getType(), "getKey", new Class[0]); + } catch (Exception e) { + LOG.error("Could not find {}#getKey", subjectField.getType(), e); + } + + if (relMethod != null) { + if (Long.class.isAssignableFrom(relMethod.getReturnType())) { + cond.setSchema(cond.getSchema() + "_id"); + schema.setType(AttrSchemaType.Long); + } + if (String.class.isAssignableFrom(relMethod.getReturnType())) { + cond.setSchema(cond.getSchema() + "_name"); + schema.setType(AttrSchemaType.String); + } + } + } + + PlainAttrValue attrValue = attrUtil.newPlainAttrValue(); + if (cond.getType() != AttributeCond.Type.LIKE + && cond.getType() != AttributeCond.Type.ISNULL + && cond.getType() != AttributeCond.Type.ISNOTNULL) { + + try { + schema.getValidator().validate(cond.getExpression(), attrValue); + } catch (ValidationException e) { + LOG.error("Could not validate expression '" + cond.getExpression() + "'", e); + return EMPTY_ATTR_QUERY; + } + } + + final StringBuilder query = new StringBuilder("SELECT DISTINCT subject_id FROM "). + append(svs.field().name).append(" WHERE "); + + fillAttributeQuery(query, attrValue, schema, cond, not, parameters, svs); + + return query.toString(); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java new file mode 100644 index 0000000..a9c9b46 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.task.Task; +import org.apache.syncope.core.persistence.jpa.entity.task.JPANotificationTask; +import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTask; +import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTask; +import org.apache.syncope.core.persistence.jpa.entity.task.JPASchedTask; +import org.apache.syncope.core.persistence.jpa.entity.task.JPASyncTask; +import org.apache.syncope.core.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.name()); + 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/d30c8526/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskExecDAO.java ---------------------------------------------------------------------- diff --git a/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskExecDAO.java b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskExecDAO.java new file mode 100644 index 0000000..d3cfcd2 --- /dev/null +++ b/syncope620/core/persistence-jpa/src/main/java/org/apache/syncope/core/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.core.persistence.jpa.dao; + +import java.util.List; +import javax.persistence.TypedQuery; +import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.dao.TaskExecDAO; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; +import org.apache.syncope.core.persistence.api.entity.task.Task; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.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); + } +}
