提交 011e54d7 编写于 作者: F Frederik Heremans

Added job-collection to REST

上级 8b1f9ac6
/* 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.rest.api.management;
import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.impl.JobQueryProperty;
import org.activiti.engine.query.QueryProperty;
import org.activiti.engine.runtime.JobQuery;
import org.activiti.rest.api.ActivitiUtil;
import org.activiti.rest.api.DataResponse;
import org.activiti.rest.api.SecuredResource;
import org.restlet.data.Form;
import org.restlet.resource.Get;
/**
* @author Frederik Heremans
*/
public class JobCollectionResource extends SecuredResource {
protected static Map<String, QueryProperty> properties;
static {
properties = new HashMap<String, QueryProperty>();
properties.put("id", JobQueryProperty.JOB_ID);
properties.put("dueDate", JobQueryProperty.DUEDATE);
properties.put("executionId", JobQueryProperty.EXECUTION_ID);
properties.put("processInstanceId", JobQueryProperty.PROCESS_INSTANCE_ID);
properties.put("retries", JobQueryProperty.RETRIES);
}
@Get
public DataResponse getJobs() {
if (authenticate() == false)
return null;
JobQuery query = ActivitiUtil.getManagementService().createJobQuery();
Form form = getQuery();
if(form.getNames().contains("id")) {
query.jobId(getQueryParameter("id", form));
}
if(form.getNames().contains("processInstanceId")) {
query.processInstanceId(getQueryParameter("processInstanceId", form));
}
if(form.getNames().contains("executionId")) {
query.executionId(getQueryParameter("executionId", form));
}
if(form.getNames().contains("processDefinitionId")) {
query.processDefinitionId(getQueryParameter("processDefinitionId", form));
}
if(form.getNames().contains("withRetriesLeft")) {
if(Boolean.TRUE.equals(getQueryParameterAsBoolean("withRetriesLeft", form))) {
query.withRetriesLeft();
}
}
if(form.getNames().contains("executable")) {
if(Boolean.TRUE.equals(getQueryParameterAsBoolean("executable", form))) {
query.executable();
}
}
if(form.getNames().contains("timersOnly")) {
if(form.getNames().contains("messagesOnly")) {
throw new ActivitiIllegalArgumentException("Only one of 'timersOnly' or 'messagesOnly' can be provided.");
}
if(Boolean.TRUE.equals(getQueryParameterAsBoolean("timersOnly", form))) {
query.timers();
}
}
if(form.getNames().contains("messagesOnly")) {
if(Boolean.TRUE.equals(getQueryParameterAsBoolean("messagesOnly", form))) {
query.messages();
}
}
if(form.getNames().contains("dueBefore")) {
query.duedateLowerThan(getQueryParameterAsDate("dueBefore", form));
}
if(form.getNames().contains("dueAfter")) {
query.duedateHigherThan(getQueryParameterAsDate("dueAfter", form));
}
if(form.getNames().contains("withException")) {
if(Boolean.TRUE.equals(getQueryParameterAsBoolean("withException", form))) {
query.withException();
}
}
if(form.getNames().contains("exceptionMessage")) {
query.exceptionMessage(getQueryParameter("exceptionMessage", form));
}
return new JobPaginateList(this).paginateList(form, query, "id", properties);
}
}
/* 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.rest.api.management;
import java.util.ArrayList;
import java.util.List;
import org.activiti.engine.runtime.Job;
import org.activiti.rest.api.AbstractPaginateList;
import org.activiti.rest.api.RestResponseFactory;
import org.activiti.rest.api.SecuredResource;
import org.activiti.rest.application.ActivitiRestServicesApplication;
/**
* @author Frederik Heremans
*/
public class JobPaginateList extends AbstractPaginateList {
private SecuredResource resource;
public JobPaginateList(SecuredResource resource) {
this.resource = resource;
}
@SuppressWarnings("rawtypes")
@Override
protected List processList(List list) {
List<JobResponse> responseList = new ArrayList<JobResponse>();
RestResponseFactory restResponseFactory = resource.getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory();
for (Object job : list) {
responseList.add(restResponseFactory.createJobResponse(resource, (Job) job));
}
return responseList;
}
}
......@@ -52,6 +52,7 @@ import org.activiti.rest.api.legacy.process.LegacyProcessInstanceResource;
import org.activiti.rest.api.legacy.process.LegacyProcessInstancesResource;
import org.activiti.rest.api.legacy.process.ProcessDefinitionsResource;
import org.activiti.rest.api.legacy.task.LegacyTaskResource;
import org.activiti.rest.api.management.JobCollectionResource;
import org.activiti.rest.api.management.JobExceptionStacktraceResource;
import org.activiti.rest.api.management.JobResource;
import org.activiti.rest.api.management.TableCollectionResource;
......@@ -160,6 +161,7 @@ public class RestServicesInit {
router.attach("/management/tables/{tableName}", TableResource.class);
router.attach("/management/tables/{tableName}/columns", TableColumnsResource.class);
router.attach("/management/tables/{tableName}/data", TableDataResource.class);
router.attach("/management/jobs", JobCollectionResource.class);
router.attach("/management/jobs/{jobId}", JobResource.class);
router.attach("/management/jobs/{jobId}/exception-stacktrace", JobExceptionStacktraceResource.class);
......
package org.activiti.rest.api.management;
import java.util.Calendar;
import java.util.Collections;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.test.Deployment;
import org.activiti.rest.BaseRestTestCase;
import org.activiti.rest.api.RestUrls;
import org.restlet.data.Status;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
/**
* Test for all REST-operations related to the Job collection and a single
* job resource.
*
* @author Frederik Heremans
*/
public class JobCollectionResourceTest extends BaseRestTestCase {
@Deployment(resources = {"org/activiti/rest/api/management/JobCollectionResourceTest.testTimerProcess.bpmn20.xml"})
public void testGetJobs() throws Exception {
Calendar hourAgo = Calendar.getInstance();
hourAgo.add(Calendar.HOUR, -1);
Calendar inAnHour = Calendar.getInstance();
inAnHour.add(Calendar.HOUR, 1);
// Start process, forcing error on job-execution
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("timerProcess",
Collections.singletonMap("error", (Object) Boolean.TRUE));
Job timerJob = managementService.createJobQuery().processInstanceId(processInstance.getId()).timers().singleResult();
assertNotNull(timerJob);
for(int i=0; i < timerJob.getRetries(); i++) {
// Force execution of job until retries are exhausted
try {
managementService.executeJob(timerJob.getId());
fail();
} catch(ActivitiException expected) {
// Ignore, we expect the exception
}
}
timerJob = managementService.createJobQuery().processInstanceId(processInstance.getId()).timers().singleResult();
assertEquals(0, timerJob.getRetries());
// Fetch the async-job (which has retries left)
Job asyncJob = managementService.createJobQuery().processInstanceId(processInstance.getId()).withRetriesLeft().singleResult();
// Test fetching all jobs
String url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION);
assertResultsPresentInDataResponse(url, asyncJob.getId(), timerJob.getId());
// Fetch using job-id
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?id=" + asyncJob.getId();
assertResultsPresentInDataResponse(url, asyncJob.getId());
// Fetch using processInstanceId
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?processInstanceId=" + processInstance.getId();
assertResultsPresentInDataResponse(url, asyncJob.getId(), timerJob.getId());
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?processInstanceId=unexisting";
assertResultsPresentInDataResponse(url);
// Fetch using executionId
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?executionId=" + asyncJob.getExecutionId();
assertResultsPresentInDataResponse(url, asyncJob.getId());
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?executionId=" + timerJob.getExecutionId();
assertResultsPresentInDataResponse(url, timerJob.getId());
// Fetch using processDefinitionId
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?processDefinitionId=" + processInstance.getProcessDefinitionId();
assertResultsPresentInDataResponse(url, timerJob.getId());
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?processDefinitionId=unexisting";
assertResultsPresentInDataResponse(url);
// Fetch using withRetriesLeft
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?withRetriesLeft=true";
assertResultsPresentInDataResponse(url, asyncJob.getId());
// Fetch using executable
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?executable=true";
assertResultsPresentInDataResponse(url, asyncJob.getId());
// Fetch using timers only
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?timersOnly=true";
assertResultsPresentInDataResponse(url, timerJob.getId());
// Combining messagesOnly with timersOnly should result in exception
ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION)
+ "?timersOnly=true&messagesOnly=true");
try {
client.get();
fail("Exception expected");
} catch(ResourceException expected) {
assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, expected.getStatus());
assertEquals("Only one of 'timersOnly' or 'messagesOnly' can be provided.", expected.getStatus().getDescription());
}
// Fetch using dueBefore
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?dueBefore=" + getISODateString(inAnHour.getTime());
assertResultsPresentInDataResponse(url, timerJob.getId());
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?dueBefore=" + getISODateString(hourAgo.getTime());
assertResultsPresentInDataResponse(url);
// Fetch using dueAfter
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?dueAfter=" + getISODateString(hourAgo.getTime());
assertResultsPresentInDataResponse(url, timerJob.getId());
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?dueAfter=" + getISODateString(inAnHour.getTime());
assertResultsPresentInDataResponse(url);
// Fetch using withException
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?withException=true";
assertResultsPresentInDataResponse(url, timerJob.getId());
// Fetch with exceptionMessage
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?exceptionMessage=" + timerJob.getExceptionMessage();
assertResultsPresentInDataResponse(url, timerJob.getId());
// Fetch with empty exceptionMessage
url = RestUrls.createRelativeResourceUrl(RestUrls.URL_JOB_COLLECTION) + "?exceptionMessage=";
assertResultsPresentInDataResponse(url);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="timerProcess">
<startEvent id="start" />
<sequenceFlow id="flow1" sourceRef="start" targetRef="fork" />
<parallelGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="task" />
<sequenceFlow sourceRef="fork" targetRef="asyncTask" />
<serviceTask id="asyncTask" activiti:async="true" activiti:expression="${test}" />
<sequenceFlow sourceRef="asyncTask" targetRef="asyncEnd" />
<endEvent id="asyncEnd" />
<userTask id="task" />
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="task">
<timerEventDefinition>
<timeDuration>PT30M</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow id="flow3" sourceRef="escalationTimer" targetRef="exclusiveGw" />
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />
<sequenceFlow sourceRef="exclusiveGw" targetRef="error">
<conditionExpression xsi:type="tFormalExpression">${error == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="exclusiveGw" targetRef="afterTimerTest" />
<scriptTask id="error" scriptFormat="unexistinglanguage">
<script>
// Using unexisting language to force error
</script>
</scriptTask>
<userTask id="afterTimerTest" />
<sequenceFlow id="flow4" sourceRef="afterTimerTest" targetRef="end" />
<sequenceFlow id="flow2" sourceRef="task" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
\ No newline at end of file
......@@ -214,7 +214,7 @@
"type" : "string"
}</programlisting>
<table>
<title>Variable query JSON paremeters</title>
<title>Variable query JSON parameters</title>
<tgroup cols="2">
<thead>
<row>
......@@ -1651,7 +1651,7 @@
}</programlisting>
</para>
<para>
All supported JSON paremeter fields allowed are exactly the same as the parameters found for <link linkend="restTasksGet">getting a collection of tasks</link>, but passed in as JSON-body arguments rather than URL-parameters to allow for more advanced querying and preventing errors with request-uri's that are too long. On top of that, the query allows
All supported JSON parameter fields allowed are exactly the same as the parameters found for <link linkend="restTasksGet">getting a collection of tasks</link>, but passed in as JSON-body arguments rather than URL-parameters to allow for more advanced querying and preventing errors with request-uri's that are too long. On top of that, the query allows
for filtering based on task and process variables. The <literal>taskVariables</literal> and <literal>processInstanceVariables</literal> are both json-arrays containing objects with the format <link linkend="restQueryVariable"> as described here.</link>
</para>
<para>
......@@ -4239,6 +4239,146 @@ Only the attachment name is required to create a new attachment.
</table>
</para>
</section>
<section>
<title>Get a list of jobs</title>
<para>
<programlisting>GET management/jobs</programlisting>
</para>
<para>
<table>
<title>URL query parameters</title>
<tgroup cols='2'>
<thead>
<row>
<entry>Parameter</entry>
<entry>Description</entry>
<entry>Type</entry>
</row>
</thead>
<tbody>
<row>
<entry>jobId</entry>
<entry>Only return job with the given id</entry>
<entry>String</entry>
</row>
<row>
<entry>processInstanceId</entry>
<entry>Only return jobs part of a process with the given id</entry>
<entry>String</entry>
</row>
<row>
<entry>executionId</entry>
<entry>Only return jobs part of an execution with the given id</entry>
<entry>String</entry>
</row>
<row>
<entry>processDefinitionId</entry>
<entry>Only return jobs with the given process definition id</entry>
<entry>String</entry>
</row>
<row>
<entry>withRetriesLeft</entry>
<entry>If <literal>true</literal>, only return jobs with retries left. If false, this parameter is ignored.</entry>
<entry>Boolean</entry>
</row>
<row>
<entry>executable</entry>
<entry>If <literal>true</literal>, only return jobs which are executable. If false, this parameter is ignored.</entry>
<entry>Boolean</entry>
</row>
<row>
<entry>timersOnly</entry>
<entry>If <literal>true</literal>, only return jobs which are timers. If false, this parameter is ignored. Cannot be used toghether with <literal>'messagesOnly'</literal>.</entry>
<entry>Boolean</entry>
</row>
<row>
<entry>messagesOnly</entry>
<entry>If <literal>true</literal>, only return jobs which are messages. If false, this parameter is ignored. Cannot be used toghether with <literal>'timersOnly'</literal></entry>
<entry>Boolean</entry>
</row>
<row>
<entry>withException</entry>
<entry>If <literal>true</literal>, only return jobs for which an exception occured while executing it. If false, this parameter is ignored.</entry>
<entry>Boolean</entry>
</row>
<row>
<entry>dueBefore</entry>
<entry>Only return jobs which are due to be executed before the given date. Jobs without duedate are never returned using this parameter.</entry>
<entry>Date</entry>
</row>
<row>
<entry>dueAfter</entry>
<entry>Only return jobs which are due to be executed after the given date. Jobs without duedate are never returned using this parameter.</entry>
<entry>Date</entry>
</row>
<row>
<entry>exceptionMessage</entry>
<entry>Only return jobs with the given exception message</entry>
<entry>String</entry>
</row>
<row>
<entry>sort</entry>
<entry>Field to sort results on, should be one of <literal>id</literal>, <literal>dueDate</literal>, <literal>executionId</literal>, <literal>processInstanceId</literal> or <literal>retries</literal>.</entry>
<entry>String</entry>
</row>
<row>
<entry namest="c1" nameend="c3"><para>The general <link linkend="restPagingAndSort">paging and sorting query-parameters</link> can be used for this URL.</para></entry>
</row>
</tbody>
</tgroup>
</table>
</para>
<para>
<emphasis role="bold">Success response body:</emphasis>
<programlisting>
{
"data":[
{
"id":"13",
"url":"http://localhost:8182/management/jobs/13",
"processInstanceId":"5",
"processInstanceUrl":"http://localhost:8182/runtime/process-instances/5",
"processDefinitionId":"timerProcess:1:4",
"processDefinitionUrl":"http://localhost:8182/repository/process-definitions/timerProcess%3A1%3A4",
"executionId":"12",
"executionUrl":"http://localhost:8182/runtime/executions/12",
"retries":0,
"exceptionMessage":"Can't find scripting engine for 'unexistinglanguage'",
"dueDate":"2013-06-07T10:00:24.653+0000"
},
...
],
"total":2,
"start":0,
"sort":"id",
"order":"asc",
"size":2
}</programlisting>
<table>
<title>Response codes</title>
<tgroup cols='2'>
<thead>
<row>
<entry>Response code</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>200</entry>
<entry>Indicates the requested jobs were returned.</entry>
</row>
<row>
<entry>400</entry>
<entry>Indicates an illegal value has been used in a url query parameter or the both <literal>'messagesOnly'</literal> and <literal>'timersOnly'</literal> are used as parameters. Status description contains additional details about the error.</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
</section>
</section>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册