ALF-4485: F107 All relevant REST APIs have the ability to exclude task and workflow types

GET workflow-definitions, GET task-instances and GET workflow-instances all now support the new 'exclude' parameter. This is a comma separated list of types to exclude from the results, for example: "wf:adhocTask,wcmwf:*"

Also fixed the filtering logic used in GET task-instances and GET workflow-instances so that multiple filter parameters now work correctly together.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22046 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2010-08-27 14:02:06 +00:00
parent aecf8c464b
commit 6dd6f37edc
8 changed files with 356 additions and 157 deletions

View File

@@ -39,9 +39,13 @@ import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.util.StringUtils;
/**
* Base class for all workflow REST API implementations.
*
* @author Nick Smith
* @author Gavin Cornwell
* @since 3.4
*/
public abstract class AbstractWorkflowWebscript extends DeclarativeWebScript
@@ -51,6 +55,7 @@ public abstract class AbstractWorkflowWebscript extends DeclarativeWebScript
public static final String PARAM_MAX_ITEMS = "maxItems";
public static final String PARAM_SKIP_COUNT = "skipCount";
public static final String PARAM_EXCLUDE = "exclude";
// used for results pagination: indicates that all items from list should be returned
public static final int DEFAULT_MAX_ITEMS = -1;
@@ -272,4 +277,126 @@ public abstract class AbstractWorkflowWebscript extends DeclarativeWebScript
return pagingResults;
}
/**
* Determines whether the given date is a match for the given filter value.
*
* @param date The date to check against
* @param filterValue The value of the filter, either an empty String or a Date object
* @param dateBeforeFilter true to test the date is before the filterValue,
* false to test the date is after the filterValue
* @return true if the date is a match for the filterValue
*/
protected boolean isDateMatchForFilter(Date date, Object filterValue, boolean dateBeforeFilter)
{
boolean match = true;
if (filterValue.equals(EMPTY))
{
if (date != null)
{
match = false;
}
}
else
{
if (date == null)
{
match = false;
}
else
{
if (dateBeforeFilter)
{
if (((Date)date).getTime() >= ((Date)filterValue).getTime())
{
match = false;
}
}
else
{
if (((Date)date).getTime() <= ((Date)filterValue).getTime())
{
match = false;
}
}
}
}
return match;
}
/**
* Helper class to check for excluded items.
*/
public class ExcludeFilter
{
private static final String WILDCARD = "*";
private List<String> exactFilters;
private List<String> wilcardFilters;
private boolean containsWildcards = false;
/**
* Creates a new ExcludeFilter
*
* @param filters Comma separated list of filters which can optionally
* contain wildcards
*/
public ExcludeFilter(String filters)
{
// tokenize the filters
String[] filterArray = StringUtils.tokenizeToStringArray(filters, ",");
// create a list of exact filters and wildcard filters
this.exactFilters = new ArrayList<String>(filterArray.length);
this.wilcardFilters = new ArrayList<String>(filterArray.length);
for (String filter : filterArray)
{
if (filter.endsWith(WILDCARD))
{
// at least one wildcard is present
this.containsWildcards = true;
// add the filter without the wildcard
this.wilcardFilters.add(filter.substring(0,
(filter.length()-WILDCARD.length())));
}
else
{
// add the exact filter
this.exactFilters.add(filter);
}
}
}
/**
* Determines whether the given item matches one of
* the filters.
*
* @param item The item to check
* @return true if the item matches one of the filters
*/
public boolean isMatch(String item)
{
// see whether there is an exact match
boolean match = this.exactFilters.contains(item);
// if there wasn't an exact match and wildcards are present
if (item != null && !match && this.containsWildcards)
{
for (String wildcardFilter : this.wilcardFilters)
{
if (item.startsWith(wildcardFilter))
{
match = true;
break;
}
}
}
return match;
}
}
}

View File

@@ -18,7 +18,6 @@
*/
package org.alfresco.repo.web.scripts.workflow;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -38,8 +37,10 @@ import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* Webscript impelementation to return workflow task instances.
*
* @author Nick Smith
*
* @author Gavin Cornwell
*/
public class TaskInstancesGet extends AbstractWorkflowWebscript
{
@@ -72,6 +73,12 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript
processDateFilter(req, PARAM_DUE_BEFORE, filters);
processDateFilter(req, PARAM_DUE_AFTER, filters);
String excludeParam = req.getParameter(PARAM_EXCLUDE);
if (excludeParam != null && excludeParam.length() > 0)
{
filters.put(PARAM_EXCLUDE, new ExcludeFilter(excludeParam));
}
List<WorkflowTask> allTasks;
if (authority != null)
@@ -205,14 +212,17 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript
return authority;
}
/*
* If workflow task matches at list one filter value or if no filter was specified, then it will be included in response
/**
* Determine if the given task should be included in the response.
*
* @param task The task to check
* @param filters The list of filters the task must match to be included
* @return true if the task matches and should therefore be returned
*/
private boolean matches(WorkflowTask task, Map<String, Object> filters)
{
// by default we assume that workflow task should be included to response
// by default we assume that workflow task should be included
boolean result = true;
boolean firstFilter = true;
for (String key : filters.keySet())
{
@@ -221,63 +231,44 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript
// 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 task to response
// based on matching to filter parameter (by default false)
if (firstFilter)
if (key.equals(PARAM_EXCLUDE))
{
result = false;
firstFilter = false;
}
boolean matches = false;
if (key.equals(PARAM_DUE_BEFORE))
{
Serializable dueDate = task.getProperties().get(WorkflowModel.PROP_DUE_DATE);
if (filterValue.equals(EMPTY))
ExcludeFilter excludeFilter = (ExcludeFilter)filterValue;
String type = task.getDefinition().getMetadata().getName().toPrefixString(this.namespaceService);
if (excludeFilter.isMatch(type))
{
if (dueDate == null)
{
matches = true;
}
result = false;
break;
}
else
}
else if (key.equals(PARAM_DUE_BEFORE))
{
Date dueDate = (Date)task.getProperties().get(WorkflowModel.PROP_DUE_DATE);
if (!isDateMatchForFilter(dueDate, filterValue, true))
{
if (dueDate == null || ((Date) dueDate).getTime() <= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_DUE_AFTER))
{
Serializable dueDate = task.getProperties().get(WorkflowModel.PROP_DUE_DATE);
Date dueDate = (Date)task.getProperties().get(WorkflowModel.PROP_DUE_DATE);
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(dueDate, filterValue, false))
{
if (dueDate == null)
{
matches = true;
}
}
else
{
if (dueDate == null || ((Date) dueDate).getTime() >= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_PRIORITY))
{
if (filterValue.equals(task.getProperties().get(WorkflowModel.PROP_PRIORITY).toString()))
if (!filterValue.equals(task.getProperties().get(WorkflowModel.PROP_PRIORITY).toString()))
{
matches = true;
result = false;
break;
}
}
// update global result
result = result || matches;
}
}

View File

@@ -34,12 +34,20 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
*
* @author Gavin Cornwell
* @author Nick Smith
* @since 3.4
*/
public class WorkflowDefinitionsGet extends AbstractWorkflowWebscript
{
@Override
protected Map<String, Object> buildModel(WorkflowModelBuilder modelBuilder, WebScriptRequest req, Status status, Cache cache)
{
ExcludeFilter excludeFilter = null;
String excludeParam = req.getParameter(PARAM_EXCLUDE);
if (excludeParam != null && excludeParam.length() > 0)
{
excludeFilter = new ExcludeFilter(excludeParam);
}
// list all workflow's definitions simple representation
List<WorkflowDefinition> workflowDefinitions = workflowService.getDefinitions();
@@ -47,7 +55,11 @@ public class WorkflowDefinitionsGet extends AbstractWorkflowWebscript
for (WorkflowDefinition workflowDefinition : workflowDefinitions)
{
results.add(modelBuilder.buildSimple(workflowDefinition));
// if present, filter out excluded definitions
if (excludeFilter == null || !excludeFilter.isMatch(workflowDefinition.getName()))
{
results.add(modelBuilder.buildSimple(workflowDefinition));
}
}
Map<String, Object> model = new HashMap<String, Object>();

View File

@@ -18,7 +18,6 @@
*/
package org.alfresco.repo.web.scripts.workflow;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -27,6 +26,7 @@ import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowTask;
@@ -35,13 +35,13 @@ import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* @author unknown
* Java backed implementation for REST API to retrieve workflow instances.
*
* @author Gavin Cornwell
* @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_PRIORITY = "priority";
@@ -59,12 +59,18 @@ public class WorkflowInstancesGet extends AbstractWorkflowWebscript
{
Map<String, String> params = req.getServiceMatch().getTemplateVars();
// get request parameters
// get filter param values
Map<String, Object> filters = new HashMap<String, Object>(9);
filters.put(PARAM_STATE, req.getParameter(PARAM_STATE));
filters.put(PARAM_INITIATOR, req.getParameter(PARAM_INITIATOR));
filters.put(PARAM_PRIORITY, req.getParameter(PARAM_PRIORITY));
String excludeParam = req.getParameter(PARAM_EXCLUDE);
if (excludeParam != null && excludeParam.length() > 0)
{
filters.put(PARAM_EXCLUDE, new ExcludeFilter(excludeParam));
}
// process all the date related parameters
processDateFilter(req, PARAM_DUE_BEFORE, filters);
processDateFilter(req, PARAM_DUE_AFTER, filters);
@@ -72,7 +78,7 @@ public class WorkflowInstancesGet extends AbstractWorkflowWebscript
processDateFilter(req, PARAM_STARTED_AFTER, filters);
processDateFilter(req, PARAM_COMPLETED_BEFORE, filters);
processDateFilter(req, PARAM_COMPLETED_AFTER, filters);
// determine if there is a definition id to filter by
String workflowDefinitionId = params.get(VAR_DEFINITION_ID);
if (workflowDefinitionId == null)
@@ -113,14 +119,17 @@ public class WorkflowInstancesGet extends AbstractWorkflowWebscript
return createResultModel(modelBuilder, req, "workflowInstances", results);
}
/*
* If workflow instance matches at list one filter value or if no filter was specified, then it will be included in response
/**
* Determine if the given workflow instance should be included in the response.
*
* @param workflowInstance The workflow instance to check
* @param filters The list of filters the task must match to be included
* @return true if the workflow matches and should therefore be returned
*/
private boolean matches(WorkflowInstance workflowInstance, Map<String, Object> filters, WorkflowModelBuilder modelBuilder)
{
// by default we assume that workflow instance should be included to response
// by default we assume that workflow instance should be included
boolean result = true;
boolean firstFilter = true;
for (String key : filters.keySet())
{
@@ -129,164 +138,122 @@ public class WorkflowInstancesGet extends AbstractWorkflowWebscript
// 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)
if (key.equals(PARAM_EXCLUDE))
{
result = false;
firstFilter = false;
ExcludeFilter excludeFilter = (ExcludeFilter)filterValue;
String type = workflowInstance.getDefinition().getName();
if (excludeFilter.isMatch(type))
{
result = false;
break;
}
}
boolean matches = false;
if (key.equals(PARAM_STATE))
else if (key.equals(PARAM_STATE))
{
WorkflowState filter = WorkflowState.getState(filterValue.toString());
if (filter != null)
{
if (filter.equals(WorkflowState.COMPLETED) && !workflowInstance.isActive() || filter.equals(WorkflowState.ACTIVE) && workflowInstance.isActive())
if (filter.equals(WorkflowState.COMPLETED) && workflowInstance.isActive() ||
filter.equals(WorkflowState.ACTIVE) && !workflowInstance.isActive())
{
matches = true;
result = false;
break;
}
}
}
else if (key.equals(PARAM_DUE_BEFORE))
{
WorkflowTask startTask = modelBuilder.getStartTaskForWorkflow(workflowInstance);
Serializable dueDate = startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
Date dueDate = (Date)startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(dueDate, filterValue, true))
{
if (dueDate == null)
{
matches = true;
}
}
else
{
if (dueDate != null && ((Date) dueDate).getTime() <= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_DUE_AFTER))
{
WorkflowTask startTask = modelBuilder.getStartTaskForWorkflow(workflowInstance);
Serializable dueDate = startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
Date dueDate = (Date)startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_DUE_DATE);
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(dueDate, filterValue, false))
{
if (dueDate == null)
{
matches = true;
}
}
else
{
if (dueDate != null && ((Date) dueDate).getTime() >= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_STARTED_BEFORE))
{
Date startDate = workflowInstance.getStartDate();
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(startDate, filterValue, true))
{
if (startDate == null)
{
matches = true;
}
}
else
{
if (startDate != null && startDate.getTime() <= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_STARTED_AFTER))
{
Date startDate = workflowInstance.getStartDate();
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(startDate, filterValue, false))
{
if (startDate == null)
{
matches = true;
}
}
else
{
if (startDate != null && startDate.getTime() >= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_COMPLETED_BEFORE))
{
Date endDate = workflowInstance.getEndDate();
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(endDate, filterValue, true))
{
if (endDate == null)
{
matches = true;
}
}
else
{
if (endDate != null && endDate.getTime() <= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_COMPLETED_AFTER))
{
Date endDate = workflowInstance.getEndDate();
if (filterValue.equals(EMPTY))
if (!isDateMatchForFilter(endDate, filterValue, false))
{
if (endDate == null)
{
matches = true;
}
}
else
{
if (endDate != null && endDate.getTime() >= ((Date) filterValue).getTime())
{
matches = true;
}
result = false;
break;
}
}
else if (key.equals(PARAM_INITIATOR))
{
if (workflowInstance.getInitiator() != null && nodeService.exists(workflowInstance.getInitiator()) &&
filterValue.equals(nodeService.getProperty(workflowInstance.getInitiator(), ContentModel.PROP_USERNAME)))
NodeRef initiator = workflowInstance.getInitiator();
if (initiator == null)
{
matches = true;
result = false;
break;
}
else
{
if (!nodeService.exists(initiator) ||
!filterValue.equals(nodeService.getProperty(workflowInstance.getInitiator(), ContentModel.PROP_USERNAME)))
{
result = false;
break;
}
}
}
else if (key.equals(PARAM_PRIORITY))
{
WorkflowTask startTask = modelBuilder.getStartTaskForWorkflow(workflowInstance);
if (startTask != null && filterValue.equals(startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_PRIORITY).toString()))
if (startTask == null || !filterValue.equals(startTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_PRIORITY).toString()))
{
matches = true;
result = false;
break;
}
}
// update global result
result = result || matches;
}
}

View File

@@ -170,6 +170,30 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
{
checkPaging(MessageFormat.format(URL_USER_TASKS, USER2) + "&maxItems=" + maxItems + "&skipCount=" + skipCount, totalItems, maxItems, skipCount);
}
// check the exclude filtering
String exclude = "wf:submitAdhocTask";
response = sendRequest(new GetRequest(URL_TASKS + "?exclude=" + exclude), 200);
assertEquals(Status.STATUS_OK, response.getStatus());
jsonStr = response.getContentAsString();
json = new JSONObject(jsonStr);
results = json.getJSONArray("data");
assertNotNull(results);
boolean adhocTasksPresent = false;
for (int i = 0; i < results.length(); i++)
{
JSONObject taskJSON = results.getJSONObject(i);
String type = taskJSON.getString("type");
if (exclude.equals(type))
{
adhocTasksPresent = true;
break;
}
}
assertFalse("Found wf:submitAdhocTask when they were supposed to be excluded", adhocTasksPresent);
}
public void testTaskInstanceGet() throws Exception
@@ -348,6 +372,7 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
JSONObject json = new JSONObject(response.getContentAsString());
JSONArray results = json.getJSONArray("data");
assertNotNull(results);
assertTrue(results.length() > 0);
for (int i = 0; i < results.length(); i++)
{
@@ -370,6 +395,57 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
assertTrue(workflowDefinitionJSON.has("description"));
assertTrue(workflowDefinitionJSON.getString("description").length() > 0);
}
// filter the workflow definitions and check they are not returned
String exclude = "jbpm$wf:adhoc";
response = sendRequest(new GetRequest(URL_WORKFLOW_DEFINITIONS + "?exclude=" + exclude), 200);
assertEquals(Status.STATUS_OK, response.getStatus());
json = new JSONObject(response.getContentAsString());
results = json.getJSONArray("data");
assertNotNull(results);
boolean adhocWorkflowPresent = false;
for (int i = 0; i < results.length(); i++)
{
JSONObject workflowDefinitionJSON = results.getJSONObject(i);
String name = workflowDefinitionJSON.getString("name");
if (exclude.equals(name))
{
adhocWorkflowPresent = true;
break;
}
}
assertFalse("Found adhoc workflow when it was supposed to be excluded", adhocWorkflowPresent);
// filter with a wildcard and ensure they all get filtered out
exclude = "jbpm$wf:adhoc, jbpm$wcmwf:*";
response = sendRequest(new GetRequest(URL_WORKFLOW_DEFINITIONS + "?exclude=" + exclude), 200);
assertEquals(Status.STATUS_OK, response.getStatus());
json = new JSONObject(response.getContentAsString());
results = json.getJSONArray("data");
assertNotNull(results);
adhocWorkflowPresent = false;
boolean wcmWorkflowsPresent = false;
for (int i = 0; i < results.length(); i++)
{
JSONObject workflowDefinitionJSON = results.getJSONObject(i);
String name = workflowDefinitionJSON.getString("name");
if (name.equals("jbpm$wf:adhoc"))
{
adhocWorkflowPresent = true;
}
if (name.startsWith("jbpm$wcmwf:"))
{
wcmWorkflowsPresent = true;
}
}
assertFalse("Found adhoc workflow when it was supposed to be excluded", adhocWorkflowPresent);
assertFalse("Found a WCM workflow when they were supposed to be excluded", wcmWorkflowsPresent);
}
public void testWorkflowInstanceGet() throws Exception
@@ -479,19 +555,21 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
{
checkSimpleWorkflowInstanceResponse(forDefinitionResult.getJSONObject(i));
}
// create a date an hour ago to test filtering
Date anHourAgo = new Date(dueDate.getTime());
anHourAgo.setHours(anHourAgo.getHours()-1);
// filter by initiator
checkFiltering(URL_WORKFLOW_INSTANCES + "?initiator=" + USER1);
// filter by startedAfter
checkFiltering(URL_WORKFLOW_INSTANCES + "?startedAfter=" + ISO8601DateFormat.format(adhocInstance.getStartDate()));
checkFiltering(URL_WORKFLOW_INSTANCES + "?startedAfter=" + ISO8601DateFormat.format(anHourAgo));
// filter by startedBefore
checkFiltering(URL_WORKFLOW_INSTANCES + "?startedBefore=" + ISO8601DateFormat.format(adhocInstance.getStartDate()));
// filter by dueAfter
Date anHourAgo = new Date(dueDate.getTime());
anHourAgo.setHours(anHourAgo.getHours()-1);
checkFiltering(URL_WORKFLOW_INSTANCES + "?dueAfter=" + ISO8601DateFormat.format(anHourAgo));
// filter by dueBefore
@@ -518,6 +596,30 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
{
checkPaging(URL_WORKFLOW_INSTANCES + "?maxItems=" + maxItems + "&skipCount=" + skipCount, totalItems, maxItems, skipCount);
}
// check the exclude filtering
String exclude = "jbpm$wf:adhoc";
response = sendRequest(new GetRequest(URL_WORKFLOW_INSTANCES + "?exclude=" + exclude), 200);
assertEquals(Status.STATUS_OK, response.getStatus());
jsonStr = response.getContentAsString();
json = new JSONObject(jsonStr);
JSONArray results = json.getJSONArray("data");
assertNotNull(results);
boolean adhocWorkflowPresent = false;
for (int i = 0; i < results.length(); i++)
{
JSONObject workflowInstanceJSON = results.getJSONObject(i);
String type = workflowInstanceJSON.getString("type");
if (exclude.equals(type))
{
adhocWorkflowPresent = true;
break;
}
}
assertFalse("Found adhoc workflows when they were supposed to be excluded", adhocWorkflowPresent);
}
public void testWorkflowInstancesForNodeGet() throws Exception