package org.jbpm.services.task.persistence; import static org.kie.internal.query.QueryParameterIdentifiers.ACTUAL_OWNER_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.ARCHIVED; import static org.kie.internal.query.QueryParameterIdentifiers.BUSINESS_ADMIN_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.CREATED_BY_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.CREATED_ON_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.DEPLOYMENT_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.EXCLUDED_OWNER_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.EXPIRATION_TIME_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.POTENTIAL_OWNER_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.PROCESS_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.PROCESS_INSTANCE_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.PROCESS_SESSION_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.SKIPPABLE; import static org.kie.internal.query.QueryParameterIdentifiers.STAKEHOLDER_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.SUB_TASKS_STRATEGY; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_ACTIVATION_TIME_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_DESCRIPTION_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_FORM_NAME_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_NAME_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_PARENT_ID_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_PRIORITY_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_STATUS_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_SUBJECT_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TASK_USER_ROLES_LIMIT_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.TYPE_LIST; import static org.kie.internal.query.QueryParameterIdentifiers.WORK_ITEM_ID_LIST; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.Tuple; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.CollectionAttribute; import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SetAttribute; import javax.persistence.metamodel.SingularAttribute; import org.jbpm.query.jpa.data.QueryCriteria; import org.jbpm.query.jpa.data.QueryParameterIdentifiersUtil; import org.jbpm.query.jpa.data.QueryWhere; import org.jbpm.query.jpa.data.QueryWhere.QueryCriteriaType; import org.jbpm.query.jpa.service.QueryModificationService; import org.jbpm.services.task.impl.model.I18NTextImpl; import org.jbpm.services.task.impl.model.I18NTextImpl_; import org.jbpm.services.task.impl.model.OrganizationalEntityImpl; import org.jbpm.services.task.impl.model.OrganizationalEntityImpl_; import org.jbpm.services.task.impl.model.PeopleAssignmentsImpl; import org.jbpm.services.task.impl.model.PeopleAssignmentsImpl_; import org.jbpm.services.task.impl.model.TaskDataImpl; import org.jbpm.services.task.impl.model.TaskDataImpl_; import org.jbpm.services.task.impl.model.TaskImpl; import org.jbpm.services.task.impl.model.TaskImpl_; import org.jbpm.services.task.impl.model.UserImpl; import org.jbpm.services.task.impl.model.UserImpl_; import org.jbpm.services.task.query.TaskSummaryImpl; import org.kie.api.task.UserGroupCallback; import org.kie.api.task.model.Status; import org.kie.api.task.model.TaskSummary; import org.kie.internal.query.QueryParameterIdentifiers; import org.kie.internal.task.api.TaskPersistenceContext; import org.kie.internal.task.api.model.SubTasksStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TaskSummaryQueryCriteriaUtil extends AbstractTaskQueryCriteriaUtil { public final static Logger logger = LoggerFactory.getLogger(TaskSummaryQueryCriteriaUtil.class); // Query Field Info ----------------------------------------------------------------------------------------------------------- public final static Map<Class, Map<String, Attribute>> criteriaAttributes = new ConcurrentHashMap<Class, Map<String, Attribute>>(); private ServiceLoader<QueryModificationService> queryModificationServiceLoader = ServiceLoader.load(QueryModificationService.class); @Override protected synchronized boolean initializeCriteriaAttributes() { if( TaskImpl_.id == null ) { // EMF/persistence has not been initialized: // When a persistence unit (EntityManagerFactory) is initialized, // the fields of classes annotated with @StaticMetamodel are filled using reflection return false; } // do not do initialization twice (slow performance, otherwise it doesn't matter) if( ! criteriaAttributes.isEmpty() ) { return true; } // TaskImpl addCriteria(criteriaAttributes, TASK_ACTIVATION_TIME_LIST, TaskImpl.class, TaskDataImpl_.activationTime); addCriteria(criteriaAttributes, ARCHIVED, TaskImpl_.archived); addCriteria(criteriaAttributes, CREATED_ON_LIST, TaskImpl.class, TaskDataImpl_.createdOn); addCriteria(criteriaAttributes, DEPLOYMENT_ID_LIST, TaskImpl.class, TaskDataImpl_.deploymentId); addCriteria(criteriaAttributes, EXPIRATION_TIME_LIST, TaskImpl.class, TaskDataImpl_.expirationTime); addCriteria(criteriaAttributes, TASK_FORM_NAME_LIST, TaskImpl_.formName); addCriteria(criteriaAttributes, PROCESS_ID_LIST, TaskImpl.class, TaskDataImpl_.processId); addCriteria(criteriaAttributes, PROCESS_INSTANCE_ID_LIST, TaskImpl.class, TaskDataImpl_.processInstanceId); addCriteria(criteriaAttributes, PROCESS_SESSION_ID_LIST, TaskImpl.class, TaskDataImpl_.processSessionId); addCriteria(criteriaAttributes, SKIPPABLE, TaskImpl.class, TaskDataImpl_.skipable); addCriteria(criteriaAttributes, TASK_STATUS_LIST, TaskImpl.class, TaskDataImpl_.status); addCriteria(criteriaAttributes, SUB_TASKS_STRATEGY, TaskImpl_.subTaskStrategy); addCriteria(criteriaAttributes, TASK_ID_LIST, TaskImpl_.id); addCriteria(criteriaAttributes, TASK_PARENT_ID_LIST, TaskImpl.class, TaskDataImpl_.parentId); addCriteria(criteriaAttributes, TYPE_LIST, TaskImpl_.taskType); addCriteria(criteriaAttributes, WORK_ITEM_ID_LIST, TaskImpl.class, TaskDataImpl_.workItemId); addCriteria(criteriaAttributes, TASK_PRIORITY_LIST, TaskImpl.class, TaskImpl_.priority); addCriteria(criteriaAttributes, TASK_DESCRIPTION_LIST, TaskImpl_.descriptions); addCriteria(criteriaAttributes, TASK_NAME_LIST, TaskImpl_.names); addCriteria(criteriaAttributes, TASK_SUBJECT_LIST, TaskImpl_.subjects); addCriteria(criteriaAttributes, ACTUAL_OWNER_ID_LIST, TaskImpl.class, TaskDataImpl_.actualOwner); addCriteria(criteriaAttributes, CREATED_BY_LIST, TaskImpl.class, TaskDataImpl_.createdBy); // initiator addCriteria(criteriaAttributes, BUSINESS_ADMIN_ID_LIST, TaskImpl.class, PeopleAssignmentsImpl_.businessAdministrators); addCriteria(criteriaAttributes, POTENTIAL_OWNER_ID_LIST, TaskImpl.class, PeopleAssignmentsImpl_.potentialOwners); addCriteria(criteriaAttributes, STAKEHOLDER_ID_LIST, TaskImpl.class, PeopleAssignmentsImpl_.taskStakeholders); addCriteria(criteriaAttributes, EXCLUDED_OWNER_ID_LIST, TaskImpl.class, PeopleAssignmentsImpl_.excludedOwners); return true; } private static final Set<String> taskUserRoleLimitingListIds = new HashSet<String>(); static { taskUserRoleLimitingListIds.add(ACTUAL_OWNER_ID_LIST); taskUserRoleLimitingListIds.add(CREATED_BY_LIST); taskUserRoleLimitingListIds.add(BUSINESS_ADMIN_ID_LIST); taskUserRoleLimitingListIds.add(POTENTIAL_OWNER_ID_LIST); taskUserRoleLimitingListIds.add(STAKEHOLDER_ID_LIST); } public TaskSummaryQueryCriteriaUtil(TaskPersistenceContext persistenceContext) { super(persistenceContext); initialize(criteriaAttributes); } public TaskSummaryQueryCriteriaUtil() { initialize(criteriaAttributes); } // Implementation specific methods -------------------------------------------------------------------------------------------- public List<TaskSummary> doCriteriaQuery(String userId, UserGroupCallback userGroupCallback, QueryWhere queryWhere) { // 1. create builder and query instances CriteriaBuilder builder = getCriteriaBuilder(); CriteriaQuery<Tuple> criteriaQuery = builder.createTupleQuery(); // 2. query base Root<TaskImpl> taskRoot = criteriaQuery.from(TaskImpl.class); criteriaQuery = criteriaQuery.multiselect( taskRoot.get(TaskImpl_.id), // 0 taskRoot.get(TaskImpl_.name), // 1 taskRoot.get(TaskImpl_.subject), // 2 taskRoot.get(TaskImpl_.description), // 3 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.status), // 4 taskRoot.get(TaskImpl_.priority), // 5 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.skipable), // 6 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.actualOwner).get(UserImpl_.id), // 7 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdBy).get(UserImpl_.id), // 8 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdOn), // 9 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.activationTime), // 10 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.expirationTime), // 11 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processId), // 12 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processSessionId), // 13 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processInstanceId), // 14 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.deploymentId), // 15 taskRoot.get(TaskImpl_.subTaskStrategy), // 16 taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.parentId) // 17 ); taskRoot.join(TaskImpl_.taskData); // added for convienence sake, since other logic expects to find this join addUserGroupsViaCallBackToQueryWhere(userGroupCallback, queryWhere.getCriteria()); // 3. check to see if there's already a user(/security)-limitation in the search checkExistingCriteriaForUserBasedLimit(queryWhere, userId, userGroupCallback); for( QueryModificationService queryModificationService : queryModificationServiceLoader ) { queryModificationService.optimizeCriteria(queryWhere); } // 4. process query criteria fillCriteriaQuery(criteriaQuery, queryWhere, builder, TaskImpl.class); // 5. retrieve result (after also applying meta-criteria) useDistinctWhenLefOuterJoinsPresent(criteriaQuery); List<Tuple> result = createQueryAndCallApplyMetaCriteriaAndGetResult(queryWhere, criteriaQuery, builder); List<TaskSummary> taskSummaryList = new ArrayList<TaskSummary>(result.size()); for( Tuple tupleRow : result ) { int i = 0; //@formatter:off TaskSummaryImpl taskSummaryImpl = new TaskSummaryImpl( // id tupleRow.get(i++, Long.class), // name, subject, description tupleRow.get(i++, String.class), tupleRow.get(i++, String.class), tupleRow.get(i++, String.class), // status, prio, skippable tupleRow.get(i++, Status.class), tupleRow.get(i++, Integer.class), tupleRow.get(i++, Boolean.class), // actual owner, created by tupleRow.get(i++, String.class), tupleRow.get(i++, String.class), // created on, activation time, expiration time tupleRow.get(i++, Date.class), tupleRow.get(i++, Date.class), tupleRow.get(i++, Date.class), // process id, process session id, process inst id, deployment id tupleRow.get(i++, String.class), tupleRow.get(i++, Long.class), tupleRow.get(i++, Long.class), tupleRow.get(i++, String.class), tupleRow.get(i++, SubTasksStrategy.class), tupleRow.get(i++, Long.class) ); //@formatter:on taskSummaryList.add(taskSummaryImpl); } return taskSummaryList; } /** * @param userGroupCallback * @param criteriaList */ private void addUserGroupsViaCallBackToQueryWhere(UserGroupCallback userGroupCallback, List<QueryCriteria> criteriaList) { if (criteriaList.isEmpty()) { return; } for (QueryCriteria criteria : criteriaList) { if (criteria.isGroupCriteria()) { addUserGroupsViaCallBackToQueryWhere(userGroupCallback, criteria.getCriteria()); } else { String critieraListId = criteria.getListId(); if (critieraListId.equals(POTENTIAL_OWNER_ID_LIST) || critieraListId.equals(ACTUAL_OWNER_ID_LIST) || critieraListId.equals(STAKEHOLDER_ID_LIST) || critieraListId.equals(BUSINESS_ADMIN_ID_LIST)) { List<String> allNewGroupIds = new ArrayList<>(); for (Object userObj : criteria.getValues()) { if (userObj instanceof String) { List<String> groupIds = userGroupCallback.getGroupsForUser(userObj.toString()); if (groupIds != null && !groupIds.isEmpty()) { allNewGroupIds.addAll(groupIds); } } } if (!allNewGroupIds.isEmpty()) { criteria.getValues().addAll(allNewGroupIds); } } } } } /** * This method checks whether or not the query *already* contains a limiting criteria (in short, a criteria that limits results * of the query) that refers to the user or (the user's groups). If there is no user/group limiting criteria, then a * {@link QueryCriteria} with the user and group ids is added to the {@link QueryWhere} instance * </p> * If this method returns true, then there is no need to add additional criteria to the query to limit the query results * to tasks that the user may see -- there is already query present in the query that takes care of this. * * @param queryWhere The {@link QueryWhere} instance containing the query criteria * @param userId The string user id of the user calling the query * @param userGroupCallback A {@link UserGroupCallback} instance in order to retrieve the groups of the given user */ private void checkExistingCriteriaForUserBasedLimit(QueryWhere queryWhere, String userId, UserGroupCallback userGroupCallback) { List<String> groupIds = userGroupCallback.getGroupsForUser(userId); Set<String> userAndGroupIds = new HashSet<String>(); if( groupIds != null ) { userAndGroupIds.addAll(groupIds); } userAndGroupIds.add(userId); if( ! criteriaListForcesUserLimitation(userAndGroupIds, queryWhere.getCriteria()) ) { addUserRolesLimitCriteria(queryWhere, userId, groupIds); } } /** * This method calls itself recursively to determine whether or not the given {@link List}<{@link QueryCriteria}> contains a * user/group limiting criteria. * * @param userAndGroupIds A set containing the user id and all group ids * @param criteriaList The {@link List} of {@link QueryCriteria} to search * @return Whether or not the {@link QueryCriteria} list contains a user/group limiting criteria */ private static boolean criteriaListForcesUserLimitation(Set<String> userAndGroupIds, List<QueryCriteria> criteriaList) { boolean userLimitiationIntersection = false; if( criteriaList.isEmpty() ) { return false; } for( QueryCriteria criteria : criteriaList ) { if( criteria.isUnion() && criteriaList.size() > 1) { return false; } if( criteria.isGroupCriteria() ) { if( criteriaListForcesUserLimitation(userAndGroupIds, criteria.getCriteria()) ) { return true; } continue; } // intersection criteria if( taskUserRoleLimitingListIds.contains(criteria.getListId()) ) { for( Object param : criteria.getParameters() ) { if( userAndGroupIds.contains(param) ) { return true; } } } } return userLimitiationIntersection; } /** * Adds an (intersecting) {@link QueryCriteria} that limits the results to results that the user is allowed to see * * @param queryWhere The {@link QueryWhere} instance that defines the query criteria * @param userId The user id * @param groupIds The user's group ids */ private void addUserRolesLimitCriteria( QueryWhere queryWhere, String userId, List<String> groupIds ) { List<QueryCriteria> newBaseCriteriaList = new ArrayList<QueryCriteria>(2); // user role limiting criteria QueryCriteria userRolesLimitingCriteria = new QueryCriteria( QueryParameterIdentifiers.TASK_USER_ROLES_LIMIT_LIST, false, QueryCriteriaType.NORMAL, 2); userRolesLimitingCriteria.setFirst(true); userRolesLimitingCriteria.getValues().add(userId); userRolesLimitingCriteria.getValues().add(groupIds); newBaseCriteriaList.add(userRolesLimitingCriteria); // original criteria list in a new group if( ! queryWhere.getCriteria().isEmpty() ) { QueryCriteria originalBaseCriteriaGroup = new QueryCriteria(false); originalBaseCriteriaGroup.setCriteria(queryWhere.getCriteria()); newBaseCriteriaList.add(originalBaseCriteriaGroup); } queryWhere.setCriteria(newBaseCriteriaList); } /* * (non-Javadoc) * @see org.jbpm.query.jpa.impl.QueryCriteriaUtil#getEntityField(javax.persistence.criteria.CriteriaQuery, java.lang.Class, java.lang.String) */ @Override protected <T> Expression getEntityField(CriteriaQuery<T> query, String listId, Attribute attr) { if( attr == null ) { return null; } Root<TaskImpl> taskRoot = null; Join<TaskImpl, TaskDataImpl> taskDataJoin = null; Join<TaskImpl, PeopleAssignmentsImpl> peopAssignJoin = null; for( Root root : query.getRoots() ) { if( TaskImpl.class.equals(root.getJavaType()) ) { taskRoot = (Root<TaskImpl>) root; for( Join<TaskImpl, ?> join : taskRoot.getJoins() ) { if( TaskDataImpl.class.equals(join.getJavaType()) ) { taskDataJoin = (Join<TaskImpl, TaskDataImpl>) join; } else if( PeopleAssignmentsImpl.class.equals(join.getJavaType()) ) { peopAssignJoin = (Join<TaskImpl, PeopleAssignmentsImpl>) join; } } } } assert taskRoot != null : "Unable to find TaskImpl Root in query!"; if( taskDataJoin == null ) { taskDataJoin = taskRoot.join(TaskImpl_.taskData); } assert taskDataJoin != null : "Unable to find TaskDataImpl Join in query!"; return taskImplSpecificGetEntityField(query, taskRoot, taskDataJoin, peopAssignJoin, listId, attr); } @SuppressWarnings("unchecked") public static <T> Expression taskImplSpecificGetEntityField(CriteriaQuery<T> query, Root<TaskImpl> taskRoot, Join<TaskImpl, TaskDataImpl> taskDataJoin, Join<TaskImpl, PeopleAssignmentsImpl> peopleAssignJoin, String listId, Attribute attr) { Expression entityField = null; if( attr != null ) { if( listId.equals(TASK_DESCRIPTION_LIST) || listId.equals(TASK_NAME_LIST) || listId.equals(TASK_SUBJECT_LIST) ) { // we can *not* query on @LOB annotated fields (and it would be inefficient for a query api to do this?) // so we query on the (max 255 char length) string equivalent fields entityField = getJoinedEntityField(taskRoot, (Attribute<TaskImpl, I18NTextImpl>) attr, I18NTextImpl_.shortText); } else if( listId.equals(ACTUAL_OWNER_ID_LIST) || listId.equals(CREATED_BY_LIST) ) { if( taskDataJoin == null ) { taskDataJoin = taskRoot.join(TaskImpl_.taskData); } // task -> taskData -> (actualOwn/createdBy) UserImpl -> id entityField = getJoinedEntityField(taskDataJoin, (Attribute<TaskDataImpl, UserImpl>) attr, UserImpl_.id); } else if( listId.equals(BUSINESS_ADMIN_ID_LIST) || listId.equals(POTENTIAL_OWNER_ID_LIST) || listId.equals(STAKEHOLDER_ID_LIST) || listId.equals(EXCLUDED_OWNER_ID_LIST) ) { if( peopleAssignJoin == null ) { peopleAssignJoin = taskRoot.join(TaskImpl_.peopleAssignments); } // task -> peopleAssignments -> (bus admin/pot owner/stake holder/excl user) OrganizationalEntityImpl -> id entityField = getJoinedEntityField(peopleAssignJoin, (Attribute<PeopleAssignmentsImpl, OrganizationalEntityImpl>) attr, OrganizationalEntityImpl_.id); } else { if( taskDataJoin == null ) { taskDataJoin = taskRoot.join(TaskImpl_.taskData); } Class attrType = attr.getDeclaringType().getJavaType(); From [] taskRoots = { taskRoot, taskDataJoin }; for( From from : taskRoots ) { if( from.getJavaType().equals(attrType) ) { if( attr != null ) { if( attr instanceof SingularAttribute ) { entityField = from.get((SingularAttribute) attr); } else if( attr instanceof PluralAttribute ) { entityField = from.get((PluralAttribute) attr); } else { throw new IllegalStateException("Unexpected attribute type when processing criteria with list id " + listId + ": " + attr.getClass().getName() ); } break; } } } } } return entityField; } /** * This retrieves the correct field ({@link Expression}) that should be used when building the {@link Predicate}. * </p> * This field is necessary because of the amount of joins and the complexity in the human-task schema. * * @param grandparentJoin This is the parent join, * for example the join between TaskDataImpl -> PeopleAssignments * @param parentJoinAttr This is the {@link Attribute} with the information over the join (from the parent) that we need to create, * for example the {@link SingularAttribute}<{@link PeopleAssignmentsImpl}, {@link OrganizationalEntityImpl}> {@link Attribute}. * @param fieldAttr This is the {@link Attribute} with the actual attribute that we create an {@link Expression} to build a {@link Predicate} for, * for example the {@link OrganizationalEntityImpl_#id} field. * @return an {@link Expression} that can be used in a predicate with the values/parameters from a {@link QueryCriteria} instance */ @SuppressWarnings("unchecked") public static <F,T> Expression getJoinedEntityField(From<?, F> grandparentJoin, Attribute<?, T> parentJoinAttr, SingularAttribute fieldAttr) { // task -> * -> origJoin -> (fieldParentAttr field in) tojoinType -> fieldAttr Class toAttrJoinType; if( parentJoinAttr instanceof SingularAttribute ) { toAttrJoinType = parentJoinAttr.getJavaType(); } else if( parentJoinAttr instanceof PluralAttribute ) { toAttrJoinType = ((PluralAttribute) parentJoinAttr).getElementType().getJavaType(); } else { String joinName = parentJoinAttr.getDeclaringType().getJavaType().getSimpleName() + "." + parentJoinAttr.getName(); throw new IllegalStateException("Unknown attribute type encountered when trying to join " + joinName ); } Join<F, T> fieldParentJoin = null; for( Join<F, ?> join : grandparentJoin.getJoins() ) { if( join.getJavaType().equals(toAttrJoinType) ) { if( join.getAttribute().equals(parentJoinAttr) ) { fieldParentJoin = (Join<F, T>) join; if( ! JoinType.INNER.equals(fieldParentJoin.getJoinType()) ) { // This a criteria set by the user (as opposed to the user-limiting criteria) -- it MUST be followed // This means that the join is not optional (LEFT) but mandatory (INNER) fieldParentJoin = null; } break; } } } if( fieldParentJoin == null ) { if( parentJoinAttr instanceof SingularAttribute) { fieldParentJoin = grandparentJoin.join((SingularAttribute) parentJoinAttr); } else if( parentJoinAttr instanceof CollectionAttribute) { fieldParentJoin = grandparentJoin.join((CollectionAttribute) parentJoinAttr); } else if( parentJoinAttr instanceof ListAttribute) { fieldParentJoin = grandparentJoin.join((ListAttribute) parentJoinAttr); } else if( parentJoinAttr instanceof SetAttribute) { fieldParentJoin = grandparentJoin.join((SetAttribute) parentJoinAttr); } else { throw new IllegalStateException("Unknown attribute type encountered when trying to join" + parentJoinAttr.getName() ); } } return fieldParentJoin.get(fieldAttr); } @Override protected <R,T> Predicate implSpecificCreatePredicateFromSingleCriteria( CriteriaQuery<R> query, CriteriaBuilder builder, Class queryType, QueryCriteria criteria, QueryWhere queryWhere) { Predicate predicate = null; String listId = criteria.getListId(); if( TASK_USER_ROLES_LIMIT_LIST.equals(listId) ) { predicate = createTaskUserRolesLimitPredicate(criteria, query, builder); } else { for( QueryModificationService queryModService : queryModificationServiceLoader ) { if( queryModService.accepts(listId) ) { return queryModService.createPredicate(criteria, query, builder); } } throw new IllegalStateException("List id " + QueryParameterIdentifiersUtil.getQueryParameterIdNameMap().get(Integer.parseInt(criteria.getListId())) + " is not supported for queries on " + TaskImpl.class.getSimpleName() + "."); } return predicate; } @SuppressWarnings("unchecked") private static <T> Predicate createTaskUserRolesLimitPredicate(QueryCriteria criteria, CriteriaQuery<T> criteriaQuery, CriteriaBuilder builder) { String userId = (String) criteria.getValues().get(0); List<String> groupIds = (List<String>) criteria.getValues().get(1); Root<TaskImpl> taskRoot = getRoot(criteriaQuery, TaskImpl.class); assert taskRoot != null : "TaskImpl Root instance could not be found in query!"; boolean groupIdsPresent = (groupIds.size() > 0); int numPredicates = groupIdsPresent ? 5 : 2; // joins that apply to the user id's Predicate [] userGroupLimitingPredicates = new Predicate[numPredicates]; userGroupLimitingPredicates[0] = builder.equal(taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.actualOwner).get(UserImpl_.id), userId); userGroupLimitingPredicates[1] = builder.equal(taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdBy).get(UserImpl_.id), userId); Join<TaskImpl,TaskDataImpl> taskDataJoin = null; Join<TaskImpl,PeopleAssignmentsImpl> peopleAssignJoin = null; if( taskRoot != null ) { for( Join<TaskImpl,?> join : taskRoot.getJoins() ) { if( join.getJavaType().equals(PeopleAssignmentsImpl.class) ) { peopleAssignJoin = (Join<TaskImpl, PeopleAssignmentsImpl>) join; } else if( join.getJavaType().equals(TaskDataImpl.class) ) { taskDataJoin = (Join<TaskImpl,TaskDataImpl>) join; } } } assert taskDataJoin != null : "TaskImpl -> TaskDataImpl join could not be found in query!"; // joins that apply to the group id's if( groupIdsPresent ) { if( peopleAssignJoin == null ) { peopleAssignJoin = taskRoot.join(TaskImpl_.peopleAssignments); } ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> [] groupJoins = getPeopleAssignmentsJoins(peopleAssignJoin); for( int i = 0; i < groupJoins.length; ++i ) { userGroupLimitingPredicates[i+2] = builder.or( builder.equal( groupJoins[i].get(OrganizationalEntityImpl_.id), userId ), groupJoins[i].get(OrganizationalEntityImpl_.id).in(groupIds) ); } } return builder.or(userGroupLimitingPredicates); } private static ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> [] getPeopleAssignmentsJoins( Join<TaskImpl,PeopleAssignmentsImpl> peopleAssignJoin) { ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> [] joins = new ListJoin[3]; for( Join<PeopleAssignmentsImpl,?> join : peopleAssignJoin.getJoins() ) { String joinFieldName = join.getAttribute().getName(); if( PeopleAssignmentsImpl_.businessAdministrators.getName().equals(joinFieldName) ) { joins[0] = (ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl>) join; } else if( PeopleAssignmentsImpl_.potentialOwners.getName().equals(joinFieldName) ) { joins[1] = (ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl>) join; } else if( PeopleAssignmentsImpl_.taskStakeholders.getName().equals(joinFieldName) ) { joins[2] = (ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl>) join; } } if( joins[0] == null ) { joins[0] = peopleAssignJoin.join(PeopleAssignmentsImpl_.businessAdministrators, JoinType.LEFT); } if( joins[1] == null ) { joins[1] = peopleAssignJoin.join(PeopleAssignmentsImpl_.potentialOwners, JoinType.LEFT); } if( joins[2] == null ) { joins[2] = peopleAssignJoin.join(PeopleAssignmentsImpl_.taskStakeholders, JoinType.LEFT); } assert joins[0] != null : "Could not find business administrators join!"; assert joins[1] != null : "Could not find potential owners join!"; assert joins[2] != null : "Could not find task stakeholders join!"; return joins; } private <T> void useDistinctWhenLefOuterJoinsPresent(CriteriaQuery<T> criteriaQuery) { boolean useDistinct = false; Root<TaskImpl> taskRoot = null; ROOTS_FOR: for( Root root : criteriaQuery.getRoots() ) { if( TaskImpl.class.equals(root.getJavaType()) ) { taskRoot = (Root<TaskImpl>) root; for( Join<TaskImpl, ?> taskJoin : taskRoot.getJoins() ) { if( PeopleAssignmentsImpl.class.equals(taskJoin.getJavaType()) ) { Join<TaskImpl, PeopleAssignmentsImpl> peopleAssignJoin = (Join<TaskImpl, PeopleAssignmentsImpl>) taskJoin; if( JoinType.LEFT.equals(peopleAssignJoin.getJoinType()) ) { useDistinct = true; break ROOTS_FOR; } for( Join peopleAssignJoinJoin : peopleAssignJoin.getJoins() ) { if( JoinType.LEFT.equals(peopleAssignJoinJoin.getJoinType()) ) { useDistinct = true; break ROOTS_FOR; } } } } } } if( useDistinct ) { criteriaQuery.distinct(true); } } @Override protected <T,R> Expression getOrderByExpression(CriteriaQuery<R> query, Class<T> queryType, String orderByListId) { List<Selection<?>> selections = query.getSelection().getCompoundSelectionItems(); Selection orderBySelection = null; if( orderByListId.equals(QueryParameterIdentifiers.TASK_ID_LIST) ) { orderBySelection = selections.get(0); } else if( orderByListId.equals(QueryParameterIdentifiers.TASK_NAME_LIST) ) { orderBySelection = selections.get(1); } else if( orderByListId.equals(QueryParameterIdentifiers.TASK_STATUS_LIST) ) { orderBySelection = selections.get(4); } else if( orderByListId.equals(QueryParameterIdentifiers.CREATED_BY_LIST) ) { orderBySelection = selections.get(8); } else if( orderByListId.equals(QueryParameterIdentifiers.CREATED_ON_LIST) ) { orderBySelection = selections.get(9); } else if( orderByListId.equals(QueryParameterIdentifiers.PROCESS_INSTANCE_ID_LIST) ) { orderBySelection = selections.get(14); } return (Expression<?>) orderBySelection; } public static <Q,T> Predicate taskSpecificCreatePredicateFromSingleCriteria( CriteriaQuery<Q> query, CriteriaBuilder builder, QueryCriteria criteria, QueryWhere queryWhere) { Predicate predicate = null; String listId = criteria.getListId(); if( TASK_USER_ROLES_LIMIT_LIST.equals(listId) ) { predicate = createTaskUserRolesLimitPredicate(criteria, query, builder); } else { throw new IllegalStateException("List id " + QueryParameterIdentifiersUtil.getQueryParameterIdNameMap().get(Integer.parseInt(criteria.getListId())) + " is not supported for queries on " + TaskImpl.class.getSimpleName() + "."); } return predicate; } }