Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AAE-28837 Missing task in task search response #1624

Merged
merged 10 commits into from
Nov 29, 2024
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;

Check notice on line 20 in activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java#L20

Unused 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;

Check notice on line 25 in activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java#L25

Unused import - org.springframework.data.domain.Page.
import org.springframework.data.domain.Pageable;

Check notice on line 26 in activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java#L26

Unused import - org.springframework.data.domain.Pageable.
import org.springframework.data.jpa.domain.Specification;

Check notice on line 27 in activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-repo/src/main/java/org/activiti/cloud/services/query/app/repository/TaskRepository.java#L27

Unused 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
Loading