Skip to content

Commit

Permalink
AAE-28837 Missing task in task search response (#1624)
Browse files Browse the repository at this point in the history
* AAE-28837 add distinct

* AAE-28537 wip

* ignore get candidate users/groups method

* AAE-28537 implementation with subquery

* AAE-28537 license header

* AAE-28537 fix issue about task candidates

* AAE-28537 improve tests

* AAE-28537 pr comments

---------

Co-authored-by: jesty <[email protected]>
  • Loading branch information
tom-dal and jesty authored Nov 29, 2024
1 parent 10d2187 commit 350332a
Show file tree
Hide file tree
Showing 31 changed files with 482 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.activiti.cloud.services.query.app.repository.QuerydslBindingsHelper.whitelist;

import com.querydsl.core.types.dsl.StringPath;
import java.util.Set;
import org.activiti.cloud.services.query.model.QTaskCandidateGroupEntity;
import org.activiti.cloud.services.query.model.TaskCandidateGroupEntity;
import org.activiti.cloud.services.query.model.TaskCandidateGroupId;
Expand All @@ -33,6 +34,8 @@ public interface TaskCandidateGroupRepository
QuerydslPredicateExecutor<TaskCandidateGroupEntity>,
QuerydslBinderCustomizer<QTaskCandidateGroupEntity>,
CrudRepository<TaskCandidateGroupEntity, TaskCandidateGroupId> {
Set<TaskCandidateGroupEntity> findByTaskIdIn(Set<String> collect);

@Override
default void customize(QuerydslBindings bindings, QTaskCandidateGroupEntity root) {
whitelist(root).apply(bindings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import static org.activiti.cloud.services.query.app.repository.QuerydslBindingsHelper.whitelist;

import com.querydsl.core.types.dsl.StringPath;
import java.util.Collection;
import java.util.Set;
import org.activiti.cloud.services.query.model.QTaskCandidateUserEntity;
import org.activiti.cloud.services.query.model.TaskCandidateUserEntity;
import org.activiti.cloud.services.query.model.TaskCandidateUserId;
Expand All @@ -33,6 +35,8 @@ public interface TaskCandidateUserRepository
QuerydslPredicateExecutor<TaskCandidateUserEntity>,
QuerydslBinderCustomizer<QTaskCandidateUserEntity>,
CrudRepository<TaskCandidateUserEntity, TaskCandidateUserId> {
Set<TaskCandidateUserEntity> findByTaskIdIn(Collection<String> taskIds);

@Override
default void customize(QuerydslBindings bindings, QTaskCandidateUserEntity root) {
whitelist(root).apply(bindings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@

import static org.activiti.cloud.services.query.app.repository.QuerydslBindingsHelper.whitelist;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.StringPath;
import java.util.Arrays;
import org.activiti.cloud.services.query.model.QTaskEntity;
import org.activiti.cloud.services.query.model.TaskEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.activiti.cloud.alfresco.data.domain.AlfrescoPagedModelAssembler;
import org.activiti.cloud.services.query.app.repository.EntityFinder;
import org.activiti.cloud.services.query.app.repository.ProcessInstanceRepository;
import org.activiti.cloud.services.query.app.repository.TaskCandidateGroupRepository;
import org.activiti.cloud.services.query.app.repository.TaskCandidateUserRepository;
import org.activiti.cloud.services.query.app.repository.TaskRepository;
import org.activiti.cloud.services.query.app.repository.VariableRepository;
import org.activiti.cloud.services.query.model.TaskEntity;
Expand Down Expand Up @@ -202,6 +204,8 @@ public ProcessDefinitionRestrictionService processDefinitionRestrictionService(
@ConditionalOnMissingBean
public TaskControllerHelper taskControllerHelper(
TaskRepository taskRepository,
TaskCandidateUserRepository taskCandidateUserRepository,
TaskCandidateGroupRepository taskCandidateGroupRepository,
ProcessVariableService processVariableService,
AlfrescoPagedModelAssembler<TaskEntity> pagedCollectionModelAssembler,
TaskRepresentationModelAssembler taskRepresentationModelAssembler,
Expand All @@ -210,6 +214,8 @@ public TaskControllerHelper taskControllerHelper(
) {
return new TaskControllerHelper(
taskRepository,
taskCandidateUserRepository,
taskCandidateGroupRepository,
processVariableService,
pagedCollectionModelAssembler,
new QueryDslPredicateAggregator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,27 @@
*/
package org.activiti.cloud.services.query.rest;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.activiti.api.runtime.shared.security.SecurityManager;
import org.activiti.cloud.services.query.app.repository.ProcessInstanceRepository;
import org.activiti.cloud.services.query.model.ProcessInstanceEntity;
import org.activiti.cloud.services.query.model.ProcessVariableKey;
import org.activiti.cloud.services.query.rest.payload.ProcessInstanceSearchRequest;
import org.activiti.cloud.services.query.rest.specification.ProcessInstanceSpecification;
import org.activiti.cloud.services.query.rest.specification.SubqueryWrappingSpecification;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -35,6 +47,9 @@ public class ProcessInstanceSearchService {

private final SecurityManager securityManager;

@PersistenceContext
private EntityManager entityManager;

public ProcessInstanceSearchService(
ProcessInstanceRepository processInstanceRepository,
ProcessVariableService processVariableService,
Expand Down Expand Up @@ -77,14 +92,38 @@ private Page<ProcessInstanceEntity> search(
Pageable pageable,
ProcessInstanceSpecification specification
) {
Page<ProcessInstanceEntity> processInstances = processInstanceRepository.findAll(
specification,
PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())
Page<ProcessInstanceEntity> processInstances = new PageImpl<>(
executeTupleQueryAndExtractTasks(getTupleQuery(specification, pageable)),
pageable,
processInstanceRepository.count(new SubqueryWrappingSpecification<>(specification))
);
processVariableService.fetchProcessVariablesForProcessInstances(
processInstances.getContent(),
processVariableKeys
);
return processInstances;
}

private TypedQuery<Tuple> getTupleQuery(ProcessInstanceSpecification taskSpecification, Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = cb.createTupleQuery();
Root<ProcessInstanceEntity> root = tupleQuery.from(ProcessInstanceEntity.class);
tupleQuery.where(taskSpecification.toPredicate(root, tupleQuery, cb));
List<Selection<?>> selections = new ArrayList<>();
selections.add(root);
tupleQuery.getOrderList().forEach(order -> selections.add(order.getExpression()));
tupleQuery.multiselect(selections.toArray(new Selection[0]));
TypedQuery<Tuple> query = entityManager.createQuery(tupleQuery);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
return query;
}

private List<ProcessInstanceEntity> executeTupleQueryAndExtractTasks(TypedQuery<Tuple> query) {
return query
.getResultList()
.stream()
.map(t -> t.get(0, ProcessInstanceEntity.class))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,38 @@
package org.activiti.cloud.services.query.rest;

import com.querydsl.core.types.Predicate;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.activiti.api.runtime.shared.security.SecurityManager;
import org.activiti.cloud.alfresco.data.domain.AlfrescoPagedModelAssembler;
import org.activiti.cloud.api.task.model.QueryCloudTask;
import org.activiti.cloud.services.query.app.repository.TaskCandidateGroupRepository;
import org.activiti.cloud.services.query.app.repository.TaskCandidateUserRepository;
import org.activiti.cloud.services.query.app.repository.TaskRepository;
import org.activiti.cloud.services.query.model.TaskCandidateGroupEntity;
import org.activiti.cloud.services.query.model.TaskCandidateUserEntity;
import org.activiti.cloud.services.query.model.TaskEntity;
import org.activiti.cloud.services.query.rest.assembler.TaskRepresentationModelAssembler;
import org.activiti.cloud.services.query.rest.payload.TaskSearchRequest;
import org.activiti.cloud.services.query.rest.predicate.QueryDslPredicateAggregator;
import org.activiti.cloud.services.query.rest.predicate.QueryDslPredicateFilter;
import org.activiti.cloud.services.query.rest.specification.SubqueryWrappingSpecification;
import org.activiti.cloud.services.query.rest.specification.TaskSpecification;
import org.activiti.cloud.services.security.TaskLookupRestrictionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;
Expand All @@ -39,6 +58,10 @@ public class TaskControllerHelper {

private final TaskRepository taskRepository;

private final TaskCandidateUserRepository taskCandidateUserRepository;

private final TaskCandidateGroupRepository taskCandidateGroupRepository;

private final ProcessVariableService processVariableService;

private final AlfrescoPagedModelAssembler<TaskEntity> pagedCollectionModelAssembler;
Expand All @@ -51,8 +74,13 @@ public class TaskControllerHelper {

private final SecurityManager securityManager;

@PersistenceContext
private EntityManager entityManager;

public TaskControllerHelper(
TaskRepository taskRepository,
TaskCandidateUserRepository taskCandidateUserRepository,
TaskCandidateGroupRepository taskCandidateGroupRepository,
ProcessVariableService processVariableService,
AlfrescoPagedModelAssembler<TaskEntity> pagedCollectionModelAssembler,
QueryDslPredicateAggregator predicateAggregator,
Expand All @@ -61,6 +89,8 @@ public TaskControllerHelper(
SecurityManager securityManager
) {
this.taskRepository = taskRepository;
this.taskCandidateUserRepository = taskCandidateUserRepository;
this.taskCandidateGroupRepository = taskCandidateGroupRepository;
this.processVariableService = processVariableService;
this.pagedCollectionModelAssembler = pagedCollectionModelAssembler;
this.predicateAggregator = predicateAggregator;
Expand Down Expand Up @@ -121,7 +151,13 @@ private PagedModel<EntityModel<QueryCloudTask>> searchTasks(
Pageable pageable,
TaskSpecification taskSpecification
) {
Page<TaskEntity> tasks = taskRepository.findAll(taskSpecification, pageable);
Page<TaskEntity> tasks = new PageImpl<>(
executeTupleQueryAndExtractTasks(getTupleQuery(taskSpecification, pageable)),
pageable,
taskRepository.count(new SubqueryWrappingSpecification<>(taskSpecification))
);
fetchTaskCandidateUsers(tasks.getContent());
fetchTaskCandidateGroups(tasks.getContent());
processVariableService.fetchProcessVariablesForTasks(
tasks.getContent(),
taskSearchRequest.processVariableKeys()
Expand Down Expand Up @@ -211,4 +247,39 @@ private Page<TaskEntity> findPageWithProcessVariables(
return taskRepository.findAll(extendedPredicate, pageable);
}
}

private TypedQuery<Tuple> getTupleQuery(TaskSpecification taskSpecification, Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = cb.createTupleQuery();
Root<TaskEntity> root = tupleQuery.from(TaskEntity.class);
tupleQuery.where(taskSpecification.toPredicate(root, tupleQuery, cb));
List<Selection<?>> selections = new ArrayList<>();
selections.add(root);
tupleQuery.getOrderList().forEach(order -> selections.add(order.getExpression()));
tupleQuery.multiselect(selections.toArray(new Selection[0]));
TypedQuery<Tuple> query = entityManager.createQuery(tupleQuery);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
return query;
}

private List<TaskEntity> executeTupleQueryAndExtractTasks(TypedQuery<Tuple> query) {
return query.getResultList().stream().map(t -> t.get(0, TaskEntity.class)).collect(Collectors.toList());
}

private void fetchTaskCandidateUsers(Collection<TaskEntity> tasks) {
Map<String, Set<TaskCandidateUserEntity>> candidatesByTaskId = taskCandidateUserRepository
.findByTaskIdIn(tasks.stream().map(TaskEntity::getId).collect(Collectors.toSet()))
.stream()
.collect(Collectors.groupingBy(TaskCandidateUserEntity::getTaskId, Collectors.toSet()));
tasks.forEach(task -> task.setTaskCandidateUsers(candidatesByTaskId.get(task.getId())));
}

private void fetchTaskCandidateGroups(Collection<TaskEntity> tasks) {
Map<String, Set<TaskCandidateGroupEntity>> candidatesByTaskId = taskCandidateGroupRepository
.findByTaskIdIn(tasks.stream().map(TaskEntity::getId).collect(Collectors.toSet()))
.stream()
.collect(Collectors.groupingBy(TaskCandidateGroupEntity::getTaskId, Collectors.toSet()));
tasks.forEach(task -> task.setTaskCandidateGroups(candidatesByTaskId.get(task.getId())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public Predicate toPredicate(
CriteriaBuilder criteriaBuilder
) {
predicates = new ArrayList<>();
query.distinct(distinct);
applyUserRestrictionFilter(root, criteriaBuilder);
applyNameFilter(root, criteriaBuilder);
applyInitiatorFilter(root);
Expand All @@ -72,7 +73,7 @@ public Predicate toPredicate(
if (!query.getResultType().equals(Long.class)) {
applySorting(
root,
root.join(ProcessInstanceEntity_.variables, JoinType.LEFT),
() -> root.join(ProcessInstanceEntity_.variables, JoinType.LEFT),
searchRequest.sort(),
query,
criteriaBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import jakarta.persistence.metamodel.SingularAttribute;
import java.util.Collection;
import java.util.Set;
import java.util.function.Supplier;
import org.activiti.cloud.dialect.CustomPostgreSQLDialect;
import org.activiti.cloud.services.query.model.ProcessVariableEntity;
import org.activiti.cloud.services.query.model.ProcessVariableEntity_;
Expand All @@ -37,6 +38,12 @@

public abstract class SpecificationSupport<T> implements Specification<T> {

protected boolean distinct = true;

public void setDistinct(boolean distinct) {
this.distinct = distinct;
}

protected void addLikeFilters(
Collection<Predicate> predicates,
Set<String> valuesToFilter,
Expand Down Expand Up @@ -165,7 +172,7 @@ protected Predicate getVariableValueCondition(

protected void applySorting(
Root<T> root,
SetJoin<T, ProcessVariableEntity> joinedProcessVars,
Supplier<SetJoin<T, ProcessVariableEntity>> joinSupplier,
CloudRuntimeEntitySort sort,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder
Expand All @@ -174,6 +181,7 @@ protected void applySorting(
validateSort(sort);
Expression<Object> orderByClause;
if (sort.isProcessVariable()) {
SetJoin<T, ProcessVariableEntity> joinedProcessVars = joinSupplier.get();
Expression<?> extractedValue = criteriaBuilder.function(
CustomPostgreSQLDialect.getExtractionFunction(sort.type()),
Object.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2017-2020 Alfresco Software, Ltd.
*
* Licensed 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.activiti.cloud.services.query.rest.specification;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import org.springframework.data.jpa.domain.Specification;

public class SubqueryWrappingSpecification<T> implements Specification<T> {

private final SpecificationSupport<T> specification;

public SubqueryWrappingSpecification(SpecificationSupport<T> specification) {
this.specification = specification;
}

@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
specification.setDistinct(false);
Subquery<T> subquery = query.subquery(root.getModel().getJavaType());
Root<T> subroot = subquery.correlate(root);
subquery.select(subroot);
subquery.select(subroot).where(specification.toPredicate(subroot, query, criteriaBuilder)).distinct(true);
return root.in(subquery);
}
}
Loading

0 comments on commit 350332a

Please sign in to comment.