diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java index 12b4c08348..89e8941b48 100644 --- a/source/java/org/alfresco/repo/workflow/TaskComponent.java +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -30,6 +30,7 @@ import java.util.Map; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.QName; @@ -67,6 +68,14 @@ public interface TaskComponent */ public List getPooledTasks(List authorities); + /** + * Query for tasks + * + * @param query the filter by which tasks are queried + * @return the list of tasks matching the specified query + */ + public List queryTasks(WorkflowTaskQuery query); + /** * Update the Properties and Associations of a Task * diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 2ce560440d..ec17d34c00 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -42,6 +42,7 @@ import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowPath; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -327,6 +328,46 @@ public class WorkflowServiceImpl implements WorkflowService return Collections.unmodifiableList(tasks); } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#queryTasks(org.alfresco.service.cmr.workflow.WorkflowTaskFilter) + */ + public List queryTasks(WorkflowTaskQuery query) + { + // extract task component to perform query + String engineId = null; + String processId = query.getProcessId(); + if (processId != null) + { + engineId = BPMEngineRegistry.getEngineId(processId); + } + String taskId = query.getTaskId(); + if (taskId != null) + { + String taskEngineId = BPMEngineRegistry.getEngineId(taskId); + if (engineId != null && !engineId.equals(taskEngineId)) + { + throw new WorkflowException("Cannot query for tasks across multiple task components: " + engineId + ", " + taskEngineId); + } + engineId = taskEngineId; + } + + // perform query + List tasks = new ArrayList(10); + String[] ids = registry.getTaskComponents(); + for (String id: ids) + { + TaskComponent component = registry.getTaskComponent(id); + // NOTE: don't bother asking task component if specific task or process id + // are in the filter and do not correspond to the component + if (engineId != null && !engineId.equals(id)) + { + continue; + } + tasks.addAll(component.queryTasks(query)); + } + return Collections.unmodifiableList(tasks); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) */ diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java index 825c19ae43..8d9c4f2d2e 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java @@ -40,6 +40,9 @@ import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowPath; import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseSpringTest; @@ -94,6 +97,29 @@ public class WorkflowServiceImplTest extends BaseSpringTest assertTrue(nodeService.hasAspect(nodeRef, WorkflowModel.ASPECT_WORKFLOW_PACKAGE)); } + public void testQueryTasks() + { + WorkflowTaskQuery filter = new WorkflowTaskQuery(); + filter.setTaskName(QName.createQName("{http://www.alfresco.org/model/wcmworkflow/1.0}submitpendingTask")); + filter.setTaskState(WorkflowTaskState.COMPLETED); + Map taskProps = new HashMap(); + taskProps.put(QName.createQName("{http://www.alfresco.org/model/bpm/1.0}workflowDescription"), "Test5"); + filter.setTaskCustomProps(taskProps); + filter.setProcessId("jbpm$48"); + filter.setProcessName(QName.createQName("{http://www.alfresco.org/model/wcmworkflow/1.0}submit")); + Map procProps = new HashMap(); + procProps.put(QName.createQName("{http://www.alfresco.org/model/bpm/1.0}workflowDescription"), "Test5"); + procProps.put(QName.createQName("companyhome"), new NodeRef("workspace://SpacesStore/3df8a9d0-ff04-11db-98da-a3c3f3149ea5")); + filter.setProcessCustomProps(procProps); + filter.setOrderBy(new WorkflowTaskQuery.OrderBy[] { WorkflowTaskQuery.OrderBy.TaskName_Asc, WorkflowTaskQuery.OrderBy.TaskState_Asc }); + List tasks = workflowService.queryTasks(filter); + System.out.println("Found " + tasks.size() + " tasks."); + for (WorkflowTask task : tasks) + { + System.out.println(task.toString()); + } + } + public void testAssociateWorkflowPackage() { // create workflow package diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 9006c121e7..8b9d11ac91 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -68,18 +68,26 @@ import org.alfresco.service.cmr.workflow.WorkflowNode; import org.alfresco.service.cmr.workflow.WorkflowPath; import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.cmr.workflow.WorkflowTransition; import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; +import org.hibernate.criterion.Conjunction; +import org.hibernate.criterion.Disjunction; +import org.hibernate.criterion.Order; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; import org.hibernate.proxy.HibernateProxy; import org.jbpm.JbpmContext; import org.jbpm.JbpmException; import org.jbpm.context.exe.ContextInstance; import org.jbpm.context.exe.TokenVariableMap; +import org.jbpm.context.exe.VariableInstance; import org.jbpm.db.GraphSession; import org.jbpm.db.TaskMgmtSession; import org.jbpm.file.def.FileDefinition; @@ -869,6 +877,229 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.TaskComponent#queryTasks(org.alfresco.service.cmr.workflow.WorkflowTaskFilter) + */ + @SuppressWarnings("unchecked") + public List queryTasks(final WorkflowTaskQuery query) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + Session session = context.getSession(); + Criteria criteria = createTaskQueryCriteria(session, query); + List tasks = criteria.list(); + + // convert tasks to appropriate service response format + List workflowTasks = new ArrayList(tasks.size()); + for (TaskInstance task : tasks) + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + return workflowTasks; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to query tasks", e); + } + } + + /** + * Construct a JBPM Hibernate query based on the Task Query provided + * + * @param session + * @param query + * @return jbpm hiberate query criteria + */ + private Criteria createTaskQueryCriteria(Session session, WorkflowTaskQuery query) + { + Criteria process = null; + Criteria task = session.createCriteria(TaskInstance.class); + + // task id + if (query.getTaskId() != null) + { + task.add(Restrictions.eq("id", getJbpmId(query.getTaskId()))); + } + + // task state + if (query.getTaskState() != null) + { + WorkflowTaskState state = query.getTaskState(); + if (state == WorkflowTaskState.IN_PROGRESS) + { + task.add(Restrictions.eq("isOpen", true)); + task.add(Restrictions.isNull("end")); + } + else if (state == WorkflowTaskState.COMPLETED) + { + task.add(Restrictions.eq("isOpen", false)); + task.add(Restrictions.isNotNull("end")); + } + } + + // task name + if (query.getTaskName() != null) + { + task.add(Restrictions.eq("name", query.getTaskName().toPrefixString(namespaceService))); + } + + // task actor + if (query.getActorId() != null) + { + task.add(Restrictions.eq("actorId", query.getActorId())); + } + + // task custom properties + if (query.getTaskCustomProps() != null) + { + Map props = query.getTaskCustomProps(); + if (props.size() > 0) + { + Criteria variables = task.createCriteria("variableInstances"); + Disjunction values = Restrictions.disjunction(); + for (Map.Entry prop : props.entrySet()) + { + Conjunction value = Restrictions.conjunction(); + value.add(Restrictions.eq("name", mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("value", prop.getValue().toString())); + values.add(value); + } + variables.add(values); + } + } + + // process active? + if (query.isActive() != null) + { + process = (process == null) ? task.createCriteria("processInstance") : process; + if (query.isActive()) + { + process.add(Restrictions.isNull("end")); + } + else + { + process.add(Restrictions.isNotNull("end")); + } + } + + // process id + if (query.getProcessId() != null) + { + process = (process == null) ? task.createCriteria("processInstance") : process; + process.add(Restrictions.eq("id", getJbpmId(query.getProcessId()))); + } + + // process name + if (query.getProcessName() != null) + { + process = (process == null) ? task.createCriteria("processInstance") : process; + Criteria processDef = process.createCriteria("processDefinition"); + processDef.add(Restrictions.eq("name", query.getProcessName().toPrefixString(namespaceService))); + } + + // process custom properties + if (query.getProcessCustomProps() != null) + { + // TODO: Due to Hibernate bug http://opensource.atlassian.com/projects/hibernate/browse/HHH-957 + // it's not possible to perform a sub-select with the criteria api. For now issue a + // secondary query and create an IN clause. + Map props = query.getProcessCustomProps(); + if (props.size() > 0) + { + // create criteria for process variables + Criteria variables = session.createCriteria(VariableInstance.class); + variables.setProjection(Property.forName("processInstance")); + Disjunction values = Restrictions.disjunction(); + for (Map.Entry prop : props.entrySet()) + { + Conjunction value = Restrictions.conjunction(); + value.add(Restrictions.eq("name", mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("value", prop.getValue().toString())); + values.add(value); + } + variables.add(values); + + // retrieve list of processes matching specified variables + List processList = variables.list(); + Object[] processIds = null; + if (processList.size() == 0) + { + processIds = new Object[] { new Long(-1) }; + } + else + { + processIds = new Object[processList.size()]; + for (int i = 0; i < processList.size(); i++) + { + processIds[i] = processList.get(i).getId(); + } + } + + // constrain tasks by process list + process = (process == null) ? task.createCriteria("processInstance") : process; + process.add(Restrictions.in("id", processIds)); + } + } + + // order by + if (query.getOrderBy() != null) + { + WorkflowTaskQuery.OrderBy[] orderBy = query.getOrderBy(); + for (WorkflowTaskQuery.OrderBy orderByPart : orderBy) + { + if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Asc) + { + task.addOrder(Order.asc("actorId")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskActor_Desc) + { + task.addOrder(Order.desc("actorId")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Asc) + { + task.addOrder(Order.asc("create")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskCreated_Desc) + { + task.addOrder(Order.desc("create")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Asc) + { + task.addOrder(Order.asc("id")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskId_Desc) + { + task.addOrder(Order.desc("id")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Asc) + { + task.addOrder(Order.asc("name")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskName_Desc) + { + task.addOrder(Order.desc("name")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Asc) + { + task.addOrder(Order.asc("end")); + } + else if (orderByPart == WorkflowTaskQuery.OrderBy.TaskState_Desc) + { + task.addOrder(Order.desc("end")); + } + } + } + + return task; + } + /** * Gets a jBPM Task Instance * @param taskSession jBPM task session diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 2840814bad..029613bf55 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -257,6 +257,15 @@ public interface WorkflowService @Auditable(parameters = {"authority"}) public List getPooledTasks(String authority); + /** + * Query for tasks + * + * @param query the filter by which tasks are queried + * @return the list of tasks matching the specified query + */ + @Auditable(parameters = {"filter"}) + public List queryTasks(WorkflowTaskQuery query); + /** * Update the Properties and Associations of a Task * diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskQuery.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskQuery.java new file mode 100644 index 0000000000..53367e5c29 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskQuery.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.workflow; + +import java.util.Map; + +import org.alfresco.service.namespace.QName; + + +/** + * Workflow Task Query + * + * Provides support for setting predicates and order by. + * + * @author davidc + */ +public class WorkflowTaskQuery +{ + // task predicates + private String taskId; + private WorkflowTaskState taskState = WorkflowTaskState.IN_PROGRESS; + private QName taskName; + private String actorId; + private Map taskCustomProps; + + // process predicates + private String processId; + private QName processName; + private Boolean active = Boolean.TRUE; + private Map processCustomProps; + + // order by + private OrderBy[] orderBy; + + + /** + * Order By Columns + */ + public enum OrderBy + { + TaskId_Asc, + TaskId_Desc, + TaskCreated_Asc, + TaskCreated_Desc, + TaskName_Asc, + TaskName_Desc, + TaskActor_Asc, + TaskActor_Desc, + TaskState_Asc, + TaskState_Desc + }; + + + /** + * @param orderBy + */ + public void setOrderBy(OrderBy[] orderBy) + { + this.orderBy = orderBy; + } + + /** + * @return + */ + public OrderBy[] getOrderBy() + { + return orderBy; + } + + /** + * @return + */ + public String getTaskId() + { + return taskId; + } + + /** + * @param taskId + */ + public void setTaskId(String taskId) + { + this.taskId = taskId; + } + + /** + * @return + */ + public Map getTaskCustomProps() + { + return taskCustomProps; + } + + /** + * @param taskCustomProps + */ + public void setTaskCustomProps(Map taskCustomProps) + { + this.taskCustomProps = taskCustomProps; + } + + /** + * @return + */ + public WorkflowTaskState getTaskState() + { + return taskState; + } + + /** + * @param taskState + */ + public void setTaskState(WorkflowTaskState taskState) + { + this.taskState = taskState; + } + + /** + * @return + */ + public QName getTaskName() + { + return taskName; + } + + /** + * @param taskName + */ + public void setTaskName(QName taskName) + { + this.taskName = taskName; + } + + /** + * @return + */ + public String getActorId() + { + return actorId; + } + + /** + * @param actorId + */ + public void setActorId(String actorId) + { + this.actorId = actorId; + } + + /** + * @return + */ + public String getProcessId() + { + return processId; + } + + /** + * @param processId + */ + public void setProcessId(String processId) + { + this.processId = processId; + } + + /** + * @return + */ + public QName getProcessName() + { + return processName; + } + + /** + * @param processName + */ + public void setProcessName(QName processName) + { + this.processName = processName; + } + + /** + * @return + */ + public Boolean isActive() + { + return active; + } + + /** + * @param active + */ + public void setActive(Boolean active) + { + this.active = active; + } + + /** + * @return + */ + public Map getProcessCustomProps() + { + return processCustomProps; + } + + /** + * @param processCustomProps + */ + public void setProcessCustomProps(Map processCustomProps) + { + this.processCustomProps = processCustomProps; + } + +}