package org.jbpm.services.task.audit.service;
import static org.jbpm.query.jpa.impl.QueryCriteriaUtil.basicCreatePredicateFromSingleCriteria;
import static org.jbpm.query.jpa.impl.QueryCriteriaUtil.getRoot;
import static org.kie.internal.query.QueryParameterIdentifiers.TASK_VARIABLE_COMBINED_ID;
import static org.kie.internal.query.QueryParameterIdentifiers.TASK_VARIABLE_NAME_ID_LIST;
import static org.kie.internal.query.QueryParameterIdentifiers.TASK_VARIABLE_VALUE_ID_LIST;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import org.jbpm.query.jpa.data.QueryCriteria;
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.audit.impl.model.TaskVariableImpl;
import org.jbpm.services.task.audit.impl.model.TaskVariableImpl_;
import org.jbpm.services.task.impl.model.TaskImpl;
import org.jbpm.services.task.impl.model.TaskImpl_;
/**
* A {@link QueryModificationService} instace for the jbpm-human-task-audit module
*/
public class TaskAuditQueryModificationService implements QueryModificationService {
private static Set<String> listIds = new HashSet<String>(3);
static {
listIds.add(TASK_VARIABLE_NAME_ID_LIST);
listIds.add(TASK_VARIABLE_VALUE_ID_LIST);
listIds.add(TASK_VARIABLE_COMBINED_ID);
};
/*
* (non-Javadoc)
* @see org.jbpm.query.jpa.service.QueryModificationService#accepts(java.lang.String)
*/
@Override
public boolean accepts( String listId ) {
return listIds.contains(listId);
}
/*
* (non-Javadoc)
* @see org.jbpm.query.jpa.service.QueryModificationService#optimizeCriteria(org.jbpm.query.jpa.data.QueryWhere)
*/
public void optimizeCriteria( QueryWhere queryWhere ) {
optimizeCriteria(queryWhere.getCriteria());
}
/**
* This method combines multiple intersecting {@link TaskVariableImpl} criteria in order
* to make sure that the number of ({@link TaskVariableImpl}) subqueries created is minimal.
* </p>
* The following logic is applied:
* </p>
* Go through the given list of {@link QueryCriteria} and if an intersecting group or criteria
* contains multiple {@link TaskVariableImpl} criteria, then replace those task variable criteria
* with a single "combined" task variable criteria. This is then later processed correctly so as to create
* only one subquery.
* </p>
* Obviously, if we run into a group criteria, recurse.
* </p>
* Continue to go through the criteria list until we've reached the end of the list: the loop
* might have broken off earlier because it hit a union criteria and stopped to process group of intersecting
* task variable criteria that had already been found. With the successive loops, all intersecting groups
* containing task variable criteria will have been removed, allowing the last loop to reach the end
* of the list.
*
* @param criteriaList The list of {@link QueryCriteria} to process
*/
public void optimizeCriteria( List<QueryCriteria> criteriaList ) {
// we don't expect subqueries with task variable criteria
Set<QueryCriteria> optimizedCriteria = Collections.newSetFromMap(new IdentityHashMap<QueryCriteria, Boolean>(1));
boolean endOfListNotYetReached = true;
while( endOfListNotYetReached ) {
Set<QueryCriteria> taskVarCriteria = Collections.newSetFromMap(new IdentityHashMap<QueryCriteria, Boolean>(2));
boolean endOfListReached = true;
for( QueryCriteria criteria : criteriaList ) {
if( ! criteria.isFirst() && criteria.isUnion() ) {
if( taskVarCriteria.size() > 1 ) {
endOfListReached = false;
break;
}
taskVarCriteria.clear();
}
// if group, recurse
if( criteria.isGroupCriteria() ) {
if( optimizedCriteria.add(criteria) ) {
optimizeCriteria(criteria.getCriteria());
}
continue;
}
String listId = criteria.getListId();
if( listId.equals(TASK_VARIABLE_NAME_ID_LIST)
|| listId.equals(TASK_VARIABLE_VALUE_ID_LIST) ) {
taskVarCriteria.add(criteria);
}
}
if( endOfListReached ) {
endOfListNotYetReached = false;
}
if( taskVarCriteria.size() > 1 ) {
Iterator<QueryCriteria> criteriaIter = criteriaList.iterator();
QueryCriteria combinedTaskVarCriteria = null;
while( criteriaIter.hasNext() ) {
QueryCriteria criteria = criteriaIter.next();
if( taskVarCriteria.contains(criteria) ) {
if( combinedTaskVarCriteria == null ) {
combinedTaskVarCriteria = criteria;
criteria = new QueryCriteria(criteria);
// combined criteria replaces the original, and thus:
// 1. KEEPS the original union flag!
// 2. KEEPS the original first flag!
combinedTaskVarCriteria.setListId(TASK_VARIABLE_COMBINED_ID);
combinedTaskVarCriteria.setType(QueryCriteriaType.NORMAL);
combinedTaskVarCriteria.getValues().clear();
combinedTaskVarCriteria.getDateValues().clear();
// processed as a normal, even though it's group
combinedTaskVarCriteria.addCriteria(criteria);
} else {
combinedTaskVarCriteria.addCriteria(criteria);
criteriaIter.remove();
}
}
if( combinedTaskVarCriteria != null && criteria.isUnion() ) {
break;
}
}
}
}
}
/*
* (non-Javadoc)
* @see org.jbpm.query.jpa.service.QueryModificationService#createPredicate(org.jbpm.query.jpa.data.QueryCriteria, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
*/
public <R> Predicate createPredicate(QueryCriteria criteria, CriteriaQuery<R> query, CriteriaBuilder builder) {
// subquery and root
Root<TaskImpl> taskRoot = getRoot(query, TaskImpl.class);
Subquery<Long> subQuery = query.subquery(Long.class);
Root<TaskVariableImpl> taskVarRoot = subQuery.from(TaskVariableImpl.class);
subQuery.select(taskVarRoot.get(TaskVariableImpl_.taskId));
// task variable predicate (in subquery)
Predicate taskVariablePredicate = null;
String listId = criteria.getListId();
if( TASK_VARIABLE_COMBINED_ID.equals(listId) ) {
List<QueryCriteria> taskVarSubCriteriaList = criteria.getCriteria();
int size = taskVarSubCriteriaList.size();
Predicate[] taskVarSubPredicates = new Predicate[size];
for( int i = 0; i < size; ++i ) {
taskVarSubPredicates[i] = createSingleTaskVariableCriteriaPredicate(
builder, taskVarRoot, taskVarSubCriteriaList.get(i));
}
taskVariablePredicate = builder.and(taskVarSubPredicates);
} else {
taskVariablePredicate = createSingleTaskVariableCriteriaPredicate(builder, taskVarRoot, criteria);
}
// add predicate to subquery
subQuery.where(taskVariablePredicate);
// create predicate for actual query that references subquery
return taskRoot.get(TaskImpl_.id).in(subQuery);
}
private static Predicate createSingleTaskVariableCriteriaPredicate(CriteriaBuilder builder, Root<TaskVariableImpl> taskVarRoot, QueryCriteria criteria) {
String listId = criteria.getListId();
Expression entityField = null;
if( TASK_VARIABLE_NAME_ID_LIST.equals(listId) ) {
entityField = taskVarRoot.get(TaskVariableImpl_.name);
} else if( TASK_VARIABLE_VALUE_ID_LIST.equals(listId) ) {
entityField = taskVarRoot.get(TaskVariableImpl_.value);
}
return basicCreatePredicateFromSingleCriteria(builder, entityField, criteria);
}
}