From 5a053460df912c0f4a1b8127fbe90d32f137b20e Mon Sep 17 00:00:00 2001 From: Gavin Cornwell Date: Fri, 6 Aug 2010 21:19:09 +0000 Subject: [PATCH] Merged BRANCHES/DEV/BELARUS/HEAD_2010_08_04 to HEAD: 21592 & 21649: Workflow REST APIs for retrieving workflow instance collections Includes: ALF-3900: F62 REST API to get all workflow instances ALF-3901: F64 REST API to get all workflow instances of a particular workflow definition ALF-3905: F65 REST API to get a filtered list of workflow instances of a particular workflow definition (by initiator, state, date, priority) ALF-3906: F63 REST API to get a filtered list of workflow instances (by initiator, status, date, priority) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21671 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../workflow/workflow-instances.get.desc.xml | 9 + .../workflow/workflow-instances.get.json.ftl | 12 ++ .../web-scripts-application-context.xml | 5 + .../workflow/WorkflowInstancesGet.java | 195 ++++++++++++++++++ .../workflow/WorkflowModelBuilder.java | 2 +- .../workflow/WorkflowModelBuilderTest.java | 36 +++- .../scripts/workflow/WorkflowRestApiTest.java | 132 ++++++++++++ 7 files changed, 387 insertions(+), 4 deletions(-) create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.desc.xml create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.json.ftl create mode 100644 source/java/org/alfresco/repo/web/scripts/workflow/WorkflowInstancesGet.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.desc.xml new file mode 100644 index 0000000000..ac680c6ba3 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.desc.xml @@ -0,0 +1,9 @@ + + Get Workflow Instance Collection + Retrieves all workflow instances, the returned list can be optionally filtered by the state of the workflow instance and by the authority that initiated the workflow instance. + /api/workflow-instances?state={state?}&initiator={initiator?}&priority={priority?} + /api/workflow-definitions/{workflow_definition_id}/workflow-instances?state={state?}&initiator={initiator?}&date={date?}&priority={priority?} + + user + required + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.json.ftl new file mode 100644 index 0000000000..02facbe967 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow-instances.get.json.ftl @@ -0,0 +1,12 @@ +<#-- Workflow Instances collection --> + +<#import "task.lib.ftl" as taskLib /> +{ + "data": + [ + <#list workflowInstances as workflowInstance> + <@taskLib.workflowInstanceJSON workflowInstance=workflowInstance /> + <#if workflowInstance_has_next>, + + ] +} \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index b522357544..349c7d13f6 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -816,6 +816,11 @@ class="org.alfresco.repo.web.scripts.workflow.WorkflowInstanceGet" parent="abstractWorkflowWebScript"> + + + diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowInstancesGet.java b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowInstancesGet.java new file mode 100644 index 0000000000..6815f0a79d --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowInstancesGet.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.web.scripts.workflow; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.springframework.extensions.surf.util.ISO8601DateFormat; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author unknown + * @since 3.4 + * + */ +public class WorkflowInstancesGet extends AbstractWorkflowWebscript +{ + + public static final String PARAM_STATE = "state"; + public static final String PARAM_INITIATOR = "initiator"; + public static final String PARAM_DATE = "date"; + public static final String PARAM_PRIORITY = "priority"; + public static final String PARAM_DEFINITION_ID = "workflow_definition_id"; + + @Override + protected Map buildModel(WorkflowModelBuilder modelBuilder, WebScriptRequest req, Status status, Cache cache) + { + Map params = req.getServiceMatch().getTemplateVars(); + + // get request parameters + Map filters = new HashMap(4); + filters.put(PARAM_STATE, req.getParameter(PARAM_STATE)); + filters.put(PARAM_INITIATOR, req.getParameter(PARAM_INITIATOR)); + filters.put(PARAM_DATE, req.getParameter(PARAM_DATE)); + filters.put(PARAM_PRIORITY, req.getParameter(PARAM_PRIORITY)); + + String workflowDefinitionId = params.get(PARAM_DEFINITION_ID); + + List workflows = new ArrayList(); + + if (workflowDefinitionId != null) + { + // list workflows for specified workflow definition + workflows.addAll(workflowService.getWorkflows(workflowDefinitionId)); + } + else + { + List workflowDefinitions = workflowService.getAllDefinitions(); + + // list workflows for all definitions + for (WorkflowDefinition workflowDefinition : workflowDefinitions) + { + workflows.addAll(workflowService.getWorkflows(workflowDefinition.getId())); + } + } + + // filter result + List> results = new ArrayList>(workflows.size()); + + for (WorkflowInstance workflow : workflows) + { + if (matches(workflow, filters, modelBuilder)) + { + results.add(modelBuilder.buildSimple(workflow)); + } + } + + Map model = new HashMap(); + // build the model for ftl + model.put("workflowInstances", results); + + return model; + } + + /* + * If workflow instance matches at list one filter value or if no filter was specified, then it will included in response + */ + private boolean matches(WorkflowInstance workflowInstance, Map filters, WorkflowModelBuilder modelBuilder) + { + // by default we assume that workflow instance should be included to response + boolean result = true; + boolean firstFilter = true; + + for (String key : filters.keySet()) + { + String filterValue = filters.get(key); + + // skip null filters (null value means that filter was not specified) + if (filterValue != null) + { + // some of the filter was specified, so the decision to include or not workflow to response + // based on matching to filter parameter (by default false) + if (firstFilter) + { + result = false; + firstFilter = false; + } + + boolean matches = false; + + if (key.equals(PARAM_STATE)) + { + WorkflowState filter = WorkflowState.getState(filterValue); + + if (filter != null) + { + if (filter.equals(WorkflowState.COMPLETED) && !workflowInstance.isActive() || filter.equals(WorkflowState.ACTIVE) && workflowInstance.isActive()) + { + matches = true; + } + } + } + else if (key.equals(PARAM_DATE)) + { + // only workflows that was started after specified time are returned + if (workflowInstance.getStartDate().getTime() >= ISO8601DateFormat.parse(filterValue.replaceAll(" ", "+")).getTime()) + { + matches = true; + } + } + else if (key.equals(PARAM_INITIATOR)) + { + if (workflowInstance.getInitiator() != null && nodeService.exists(workflowInstance.getInitiator()) && filterValue.equals(nodeService.getProperty(workflowInstance.getInitiator(), ContentModel.PROP_USERNAME))) + { + matches = true; + } + } + else if (key.equals(PARAM_PRIORITY)) + { + WorkflowTask startTask = modelBuilder.getStartTaskForWorkflow(workflowInstance); + + if (startTask != null && filterValue.equals(startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_PRIORITY).toString())) + { + matches = true; + } + } + // update global result + result = result || matches; + } + } + + return result; + } + + private enum WorkflowState + { + ACTIVE ("active"), + COMPLETED ("completed"); + + String value; + + WorkflowState(String value) + { + this.value = value; + } + + static WorkflowState getState(String value) + { + for (WorkflowState state : WorkflowState.values()) + { + if (state.value.equals(value)) + { + return state; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java index 76d571d2d5..597431668a 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java @@ -227,7 +227,7 @@ public class WorkflowModelBuilder model.put(TASK_WORKFLOW_INSTANCE_END_DATE, ISO8601DateFormat.format(workflowInstance.getEndDate())); } - if (workflowInstance.getInitiator() == null) + if (workflowInstance.getInitiator() == null || !nodeService.exists(workflowInstance.getInitiator())) { model.put(TASK_WORKFLOW_INSTANCE_INITIATOR, null); } diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilderTest.java b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilderTest.java index f0fe02b997..3e01346d4b 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilderTest.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilderTest.java @@ -217,6 +217,33 @@ public class WorkflowModelBuilderTest extends TestCase assertEquals(workflowDefinition.getDescription(), model.get(WorkflowModelBuilder.WORKFLOW_DEFINITION_DESCRIPTION)); } + @SuppressWarnings("unchecked") + public void testBuildWorkflowInstance() throws Exception + { + WorkflowInstance workflowInstance = makeWorkflowInstance(null); + + Map model = builder.buildSimple(workflowInstance); + + assertEquals(workflowInstance.getId(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_ID)); + assertTrue(model.containsKey(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_URL)); + assertEquals(workflowInstance.getDefinition().getName(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_NAME)); + assertEquals(workflowInstance.getDefinition().getTitle(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_TITLE)); + assertEquals(workflowInstance.getDefinition().getDescription(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_DESCRIPTION)); + assertEquals(workflowInstance.isActive(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_IS_ACTIVE)); + assertEquals(ISO8601DateFormat.format(workflowInstance.getStartDate()), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_START_DATE)); + assertEquals(ISO8601DateFormat.format(workflowInstance.getEndDate()), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_END_DATE)); + + Map initiator = (Map) model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_INITIATOR); + if (initiator != null) + { + assertEquals(userName, initiator.get(WorkflowModelBuilder.PERSON_USER_NAME)); + assertEquals(firstName, initiator.get(WorkflowModelBuilder.PERSON_FIRST_NAME)); + assertEquals(lastName, initiator.get(WorkflowModelBuilder.PERSON_LAST_NAME)); + } + + assertTrue(model.containsKey(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_DEFINITION_URL)); + } + @SuppressWarnings("unchecked") public void testBuildWorkflowInstanceDetailed() throws Exception { @@ -239,9 +266,12 @@ public class WorkflowModelBuilderTest extends TestCase assertEquals(ISO8601DateFormat.format(workflowInstance.getEndDate()), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_END_DATE)); Map initiator = (Map) model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_INITIATOR); - assertEquals(userName, initiator.get(WorkflowModelBuilder.PERSON_USER_NAME)); - assertEquals(firstName, initiator.get(WorkflowModelBuilder.PERSON_FIRST_NAME)); - assertEquals(lastName, initiator.get(WorkflowModelBuilder.PERSON_LAST_NAME)); + if (initiator != null) + { + assertEquals(userName, initiator.get(WorkflowModelBuilder.PERSON_USER_NAME)); + assertEquals(firstName, initiator.get(WorkflowModelBuilder.PERSON_FIRST_NAME)); + assertEquals(lastName, initiator.get(WorkflowModelBuilder.PERSON_LAST_NAME)); + } assertEquals(workflowInstance.getContext().toString(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_CONTEXT)); assertEquals(workflowInstance.getWorkflowPackage().toString(), model.get(WorkflowModelBuilder.TASK_WORKFLOW_INSTANCE_PACKAGE)); diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowRestApiTest.java index 4244f11ce8..7636edd47c 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowRestApiTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.web.scripts.workflow; import java.io.Serializable; +import java.text.MessageFormat; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -66,6 +67,7 @@ public class WorkflowRestApiTest extends BaseWebScriptTest private static final String URL_TASKS = "api/task-instances"; private static final String URL_WORKFLOW_DEFINITIONS = "api/workflow-definitions"; private static final String URL_WORKFLOW_INSTANCES = "api/workflow-instances"; + private static final String URL_WORKFLOW_INSTANCES_FOR_DEFINITION = "api/workflow-definitions/{0}/workflow-instances"; private TestPersonManager personManager; private WorkflowService workflowService; @@ -392,6 +394,99 @@ public class WorkflowRestApiTest extends BaseWebScriptTest assertTrue(tasks.length() > 1); } + public void testWorkflowInstancesGet() throws Exception + { + //Start workflow as USER1 and assign task to USER2. + personManager.setUser(USER1); + WorkflowDefinition adhocDef = workflowService.getDefinitionByName("jbpm$wf:adhoc"); + Map params = new HashMap(); + params.put(WorkflowModel.ASSOC_ASSIGNEE, personManager.get(USER2)); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + params.put(WorkflowModel.ASSOC_PACKAGE, packageRef); + params.put(WorkflowModel.PROP_CONTEXT, packageRef); + + WorkflowPath adhocPath = workflowService.startWorkflow(adhocDef.getId(), params); + WorkflowTask startTask = workflowService.getTasksForWorkflowPath(adhocPath.getId()).get(0); + + WorkflowInstance adhocInstance = startTask.getPath().getInstance(); + + // Get Workflow Instance Collection + Response response = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES), 200); + assertEquals(Status.STATUS_OK, response.getStatus()); + String jsonStr = response.getContentAsString(); + JSONObject json = new JSONObject(jsonStr); + JSONArray result = json.getJSONArray("data"); + assertNotNull(result); + + for (int i = 0; i < result.length(); i++) + { + checkSimpleWorkflowInstanceResponse(result.getJSONObject(i)); + } + + Response forDefinitionResponse = sendRequest(new GetRequest(MessageFormat.format(URL_WORKFLOW_INSTANCES_FOR_DEFINITION, adhocDef.getId())), 200); + assertEquals(Status.STATUS_OK, forDefinitionResponse.getStatus()); + String forDefinitionJsonStr = forDefinitionResponse.getContentAsString(); + JSONObject forDefinitionJson = new JSONObject(forDefinitionJsonStr); + JSONArray forDefinitionResult = forDefinitionJson.getJSONArray("data"); + assertNotNull(forDefinitionResult); + + for (int i = 0; i < forDefinitionResult.length(); i++) + { + checkSimpleWorkflowInstanceResponse(forDefinitionResult.getJSONObject(i)); + } + + // filter by initiator + String initiatorFilter = "?initiator=" + USER1; + Response initiatorFilteredResponse = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES + initiatorFilter), 200); + + assertEquals(Status.STATUS_OK, initiatorFilteredResponse.getStatus()); + String initiatorFilteredJsonStr = initiatorFilteredResponse.getContentAsString(); + JSONObject initiatorFilteredJson = new JSONObject(initiatorFilteredJsonStr); + JSONArray initiatorFilteredResult = initiatorFilteredJson.getJSONArray("data"); + assertNotNull(initiatorFilteredResult); + + assertEquals(1, initiatorFilteredResult.length()); + assertEquals(adhocInstance.getId(), initiatorFilteredResult.getJSONObject(0).getString("id")); + + // filter by date + String dateFilter = "?date=" + ISO8601DateFormat.format(adhocInstance.startDate); + Response dateFilteredResponse = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES + dateFilter), 200); + + assertEquals(Status.STATUS_OK, dateFilteredResponse.getStatus()); + String dateFilteredJsonStr = dateFilteredResponse.getContentAsString(); + JSONObject dateFilteredJson = new JSONObject(dateFilteredJsonStr); + JSONArray dateFilteredResult = dateFilteredJson.getJSONArray("data"); + assertNotNull(dateFilteredResult); + + assertTrue(dateFilteredResult.length() > 0); + + // filter by priority + String priorityFilter = "?priority=1"; + Response priorityFilteredResponse = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES + priorityFilter), 200); + + assertEquals(Status.STATUS_OK, priorityFilteredResponse.getStatus()); + String priorityFilteredJsonStr = priorityFilteredResponse.getContentAsString(); + JSONObject priorityFilteredJson = new JSONObject(priorityFilteredJsonStr); + JSONArray priorityFilteredResult = priorityFilteredJson.getJSONArray("data"); + assertNotNull(priorityFilteredResult); + + assertTrue(priorityFilteredResult.length() > 1); + + // filter by state + String stateFilter = "?state=active"; + Response stateFilteredResponse = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES + stateFilter), 200); + + assertEquals(Status.STATUS_OK, stateFilteredResponse.getStatus()); + String stateFilteredJsonStr = stateFilteredResponse.getContentAsString(); + JSONObject stateFilteredJson = new JSONObject(stateFilteredJsonStr); + JSONArray stateFilteredResult = stateFilteredJson.getJSONArray("data"); + assertNotNull(stateFilteredResult); + + assertTrue(stateFilteredResult.length() > 1); + } + @Override protected void setUp() throws Exception { @@ -449,4 +544,41 @@ public class WorkflowRestApiTest extends BaseWebScriptTest } } } + + private void checkSimpleWorkflowInstanceResponse(JSONObject json) throws JSONException + { + assertTrue(json.has("id")); + assertTrue(json.getString("id").length() > 0); + + assertTrue(json.has("url")); + assertTrue(json.getString("url").startsWith(URL_WORKFLOW_INSTANCES)); + + assertTrue(json.has("name")); + assertTrue(json.getString("name").length() > 0); + + assertTrue(json.has("title")); + assertTrue(json.getString("title").length() > 0); + + assertTrue(json.has("description")); + assertTrue(json.getString("description").length() > 0); + + assertTrue(json.has("isActive")); + + assertTrue(json.has("startDate")); + assertTrue(json.getString("startDate").length() > 0); + + assertTrue(json.has("endDate")); + + assertTrue(json.has("initiator")); + Object initiator = json.get("initiator"); + if (!initiator.equals(JSONObject.NULL)) + { + assertTrue(((JSONObject)initiator).has("userName")); + assertTrue(((JSONObject)initiator).has("firstName")); + assertTrue(((JSONObject)initiator).has("lastName")); + } + + assertTrue(json.has("definitionUrl")); + assertTrue(json.getString("definitionUrl").startsWith(URL_WORKFLOW_DEFINITIONS)); + } }