diff --git a/engine-rest/engine-rest-openapi/src/main/templates/paths/process-instance/{id}/comment/get.ftl b/engine-rest/engine-rest-openapi/src/main/templates/paths/process-instance/{id}/comment/get.ftl new file mode 100644 index 00000000000..58f20a2616b --- /dev/null +++ b/engine-rest/engine-rest-openapi/src/main/templates/paths/process-instance/{id}/comment/get.ftl @@ -0,0 +1,77 @@ +<#macro endpoint_macro docsUrl=""> +{ + + <@lib.endpointInfo + id = "getProcessInstanceComments" + tag = "Process Instance" + summary = "Get Process Instance Comments" + desc = "Gets the comments for a process instance by id." /> + + "parameters" : [ + + <@lib.parameter + name = "id" + location = "path" + type = "string" + required = true + last = true + desc = "The id of the process instance to retrieve the comments for." /> + + ], + + "responses" : { + + <@lib.response + code = "200" + dto = "CommentDto" + array = true + desc = "Request successful." + examples = ['"example-1": { + "summary": "GET /process-instance/aProcessInstanceId/comment", + "value": [ + { + "id": "commentId", + "userId": "userId", + "taskId": "aTaskId", + "processInstanceId": "aProcessInstanceId", + "time": "2013-01-02T21:37:03.764+0200", + "message": "message", + "removalTime": "2018-02-10T14:33:19.000+0200", + "rootProcessInstanceId": "aRootProcessInstanceId" + }, + { + "id": "anotherCommentId", + "userId": "anotherUserId", + "taskId": "aTaskId", + "processInstanceId": "aProcessInstanceId", + "time": "2013-02-23T20:37:43.975+0200", + "message": "anotherMessage", + "removalTime": "2018-02-10T14:33:19.000+0200", + "rootProcessInstanceId": "aRootProcessInstanceId" + }, + { + "id": "yetAnotherCommentId", + "userId": "yetAnotherUserId", + "taskId": "aTaskId", + "processInstanceId": "aProcessInstanceId", + "time": "2013-04-21T10:15:23.764+0200", + "message": "yetAnotherMessage", + "removalTime": "2018-02-10T14:33:19.000+0200", + "rootProcessInstanceId": "aRootProcessInstanceId" + } + ] + }' + ] /> + + <@lib.response + code = "404" + dto = "ExceptionDto" + last = true + desc = "No process instance exists for the given process instance id. See the + [Introduction](${docsUrl}/reference/rest/overview/#error-handling) + for the error response format." /> + + } +} + + \ No newline at end of file diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceCommentResource.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceCommentResource.java new file mode 100644 index 00000000000..50cb90b189d --- /dev/null +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceCommentResource.java @@ -0,0 +1,33 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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.camunda.bpm.engine.rest.sub.runtime; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.camunda.bpm.engine.rest.dto.task.CommentDto; + +public interface ProcessInstanceCommentResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + List getComments(); + +} diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceResource.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceResource.java index 47b3e68069f..a4a5394a1b7 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceResource.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/ProcessInstanceResource.java @@ -69,4 +69,7 @@ void deleteProcessInstance(@QueryParam("skipCustomListeners") @DefaultValue("fal @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) BatchDto modifyProcessInstanceAsync(ProcessInstanceModificationDto dto); + + @Path("/comment") + ProcessInstanceCommentResource getProcessInstanceCommentResource(); } diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceCommentResourceImpl.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceCommentResourceImpl.java new file mode 100644 index 00000000000..aec71d23e23 --- /dev/null +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceCommentResourceImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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.camunda.bpm.engine.rest.sub.runtime.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.core.Response.Status; + +import org.camunda.bpm.engine.IdentityService; +import org.camunda.bpm.engine.ProcessEngine; +import org.camunda.bpm.engine.history.HistoricProcessInstance; +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.camunda.bpm.engine.impl.identity.Authentication; +import org.camunda.bpm.engine.rest.dto.task.CommentDto; +import org.camunda.bpm.engine.rest.exception.InvalidRequestException; +import org.camunda.bpm.engine.rest.sub.runtime.ProcessInstanceCommentResource; +import org.camunda.bpm.engine.task.Comment; + +public class ProcessInstanceCommentResourceImpl implements ProcessInstanceCommentResource { + + private ProcessEngine engine; + private String processInstanceId; + + public ProcessInstanceCommentResourceImpl(ProcessEngine engine, String processInstanceId) { + this.engine = engine; + this.processInstanceId = processInstanceId; + } + + public List getComments() { + if (!isHistoryEnabled()) { + return Collections.emptyList(); + } + + ensureProcessInstanceExists(Status.NOT_FOUND); + + List processInstanceComments = engine.getTaskService().getProcessInstanceComments(processInstanceId); + + List comments = new ArrayList(); + for (Comment comment : processInstanceComments) { + comments.add(CommentDto.fromComment(comment)); + } + + return comments; + } + + private boolean isHistoryEnabled() { + IdentityService identityService = engine.getIdentityService(); + Authentication currentAuthentication = identityService.getCurrentAuthentication(); + try { + identityService.clearAuthentication(); + int historyLevel = engine.getManagementService().getHistoryLevel(); + return historyLevel > ProcessEngineConfigurationImpl.HISTORYLEVEL_NONE; + } finally { + identityService.setAuthentication(currentAuthentication); + } + } + + private void ensureProcessInstanceExists(Status status) { + HistoricProcessInstance historicProcessInstance = engine.getHistoryService().createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + if (historicProcessInstance == null) { + throw new InvalidRequestException(status, "No process instance found for id " + processInstanceId); + } + } + +} \ No newline at end of file diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceResourceImpl.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceResourceImpl.java index d87444e6ba9..642af269336 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceResourceImpl.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/sub/runtime/impl/ProcessInstanceResourceImpl.java @@ -32,6 +32,7 @@ import org.camunda.bpm.engine.rest.dto.runtime.modification.ProcessInstanceModificationDto; import org.camunda.bpm.engine.rest.exception.InvalidRequestException; import org.camunda.bpm.engine.rest.sub.VariableResource; +import org.camunda.bpm.engine.rest.sub.runtime.ProcessInstanceCommentResource; import org.camunda.bpm.engine.rest.sub.runtime.ProcessInstanceResource; import org.camunda.bpm.engine.runtime.ActivityInstance; import org.camunda.bpm.engine.runtime.ProcessInstance; @@ -154,4 +155,10 @@ public BatchDto modifyProcessInstanceAsync(ProcessInstanceModificationDto dto) { throw new InvalidRequestException(Status.BAD_REQUEST, "The provided instuctions are invalid."); } + + @Override + public ProcessInstanceCommentResource getProcessInstanceCommentResource() { + return new ProcessInstanceCommentResourceImpl(engine, processInstanceId); + } + } diff --git a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/ProcessInstanceRestServiceInteractionTest.java b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/ProcessInstanceRestServiceInteractionTest.java index a30467ab74d..44c49dbc88d 100644 --- a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/ProcessInstanceRestServiceInteractionTest.java +++ b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/ProcessInstanceRestServiceInteractionTest.java @@ -19,10 +19,13 @@ import static io.restassured.RestAssured.given; import static org.camunda.bpm.engine.rest.helper.MockProvider.EXAMPLE_TASK_ID; import static org.camunda.bpm.engine.rest.helper.MockProvider.createMockBatch; +import static org.camunda.bpm.engine.rest.helper.MockProvider.createMockHistoricProcessInstance; import static org.camunda.bpm.engine.rest.util.DateTimeUtils.DATE_FORMAT_WITH_TIMEZONE; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.mockito.hamcrest.MockitoHamcrest.argThat; @@ -31,6 +34,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -40,17 +44,24 @@ import org.assertj.core.api.Assertions; import org.camunda.bpm.engine.AuthorizationException; import org.camunda.bpm.engine.BadUserRequestException; +import org.camunda.bpm.engine.HistoryService; +import org.camunda.bpm.engine.ManagementService; import org.camunda.bpm.engine.ProcessEngineException; +import org.camunda.bpm.engine.TaskService; import org.camunda.bpm.engine.batch.Batch; import org.camunda.bpm.engine.exception.NotFoundException; import org.camunda.bpm.engine.exception.NullValueException; import org.camunda.bpm.engine.history.HistoricProcessInstance; import org.camunda.bpm.engine.history.HistoricProcessInstanceQuery; +import org.camunda.bpm.engine.history.HistoricTaskInstance; +import org.camunda.bpm.engine.history.HistoricTaskInstanceQuery; import org.camunda.bpm.engine.impl.HistoricProcessInstanceQueryImpl; import org.camunda.bpm.engine.impl.HistoryServiceImpl; import org.camunda.bpm.engine.impl.ManagementServiceImpl; import org.camunda.bpm.engine.impl.RuntimeServiceImpl; import org.camunda.bpm.engine.impl.batch.BatchEntity; +import org.camunda.bpm.engine.impl.calendar.DateTimeUtil; +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.camunda.bpm.engine.impl.util.IoUtil; import org.camunda.bpm.engine.rest.dto.VariableValueDto; import org.camunda.bpm.engine.rest.dto.batch.BatchDto; @@ -86,6 +97,7 @@ import org.camunda.bpm.engine.runtime.UpdateProcessInstanceSuspensionStateSelectBuilder; import org.camunda.bpm.engine.runtime.UpdateProcessInstanceSuspensionStateTenantBuilder; import org.camunda.bpm.engine.runtime.UpdateProcessInstancesSuspensionStateBuilder; +import org.camunda.bpm.engine.task.Comment; import org.camunda.bpm.engine.variable.VariableMap; import org.camunda.bpm.engine.variable.Variables; import org.camunda.bpm.engine.variable.type.SerializableValueType; @@ -122,6 +134,7 @@ public class ProcessInstanceRestServiceInteractionTest extends protected static final String PROCESS_INSTANCE_URL = TEST_RESOURCE_ROOT_PATH + "/process-instance"; protected static final String SINGLE_PROCESS_INSTANCE_URL = PROCESS_INSTANCE_URL + "/{id}"; protected static final String PROCESS_INSTANCE_VARIABLES_URL = SINGLE_PROCESS_INSTANCE_URL + "/variables"; + protected static final String PROCESS_INSTANCE_COMMENTS_URL = SINGLE_PROCESS_INSTANCE_URL + "/comment"; protected static final String DELETE_PROCESS_INSTANCES_ASYNC_URL = PROCESS_INSTANCE_URL + "/delete"; protected static final String DELETE_PROCESS_INSTANCES_ASYNC_HIST_QUERY_URL = PROCESS_INSTANCE_URL + "/delete-historic-query-based"; protected static final String SET_JOB_RETRIES_ASYNC_URL = PROCESS_INSTANCE_URL + "/job-retries"; @@ -158,9 +171,13 @@ public class ProcessInstanceRestServiceInteractionTest extends .objectTypeName(ExampleVariableObject.class.getName())); } + private List mockTaskComments; + private HistoricProcessInstanceQuery historicProcessInstanceQueryMock; + private RuntimeServiceImpl runtimeServiceMock; private ManagementServiceImpl mockManagementService; private HistoryServiceImpl historyServiceMock; + private TaskService taskServiceMock; private UpdateProcessInstanceSuspensionStateTenantBuilder mockUpdateSuspensionStateBuilder; private UpdateProcessInstanceSuspensionStateSelectBuilder mockUpdateSuspensionStateSelectBuilder; @@ -193,13 +210,34 @@ public void setUpRuntimeData() { when(mockUpdateSuspensionStateSelectBuilder.byProcessInstanceQuery(any())).thenReturn(mockUpdateProcessInstancesSuspensionStateBuilder); when(mockUpdateSuspensionStateSelectBuilder.byHistoricProcessInstanceQuery(any())).thenReturn(mockUpdateProcessInstancesSuspensionStateBuilder); + // tasks and task service + taskServiceMock = mock(TaskService.class); + when(processEngine.getTaskService()).thenReturn(taskServiceMock); + + mockTaskComments = MockProvider.createMockTaskComments(); + when(taskServiceMock.getProcessInstanceComments(EXAMPLE_PROCESS_INSTANCE_ID)).thenReturn(mockTaskComments); + + historicProcessInstanceQueryMock = mock(HistoricProcessInstanceQuery.class); + when(historyServiceMock.createHistoricProcessInstanceQuery()).thenReturn(historicProcessInstanceQueryMock); + when(historicProcessInstanceQueryMock.processInstanceId(eq(EXAMPLE_PROCESS_INSTANCE_ID))).thenReturn(historicProcessInstanceQueryMock); + HistoricProcessInstance historicProcessInstanceMock = createMockHistoricProcessInstance(); + when(historicProcessInstanceQueryMock.singleResult()).thenReturn(historicProcessInstanceMock); + // runtime service when(processEngine.getRuntimeService()).thenReturn(runtimeServiceMock); when(processEngine.getManagementService()).thenReturn(mockManagementService); when(processEngine.getHistoryService()).thenReturn(historyServiceMock); - + } + public void mockHistoryFull() { + when(mockManagementService.getHistoryLevel()).thenReturn(ProcessEngineConfigurationImpl.HISTORYLEVEL_FULL); + } + + public void mockHistoryDisabled() { + when(mockManagementService.getHistoryLevel()).thenReturn(ProcessEngineConfigurationImpl.HISTORYLEVEL_NONE); + } + @Test public void testGetActivityInstanceTree() { Response response = given().pathParam("id", MockProvider.EXAMPLE_PROCESS_INSTANCE_ID) @@ -620,6 +658,39 @@ public void testGetVariablesWithNullValue() { Assert.assertEquals("Should return exactly one variable", 1, response.jsonPath().getMap("").size()); } + @Test + public void testGetProcessInstanceComments() { + mockHistoryFull(); + + Response response = given() + .pathParam("id", EXAMPLE_PROCESS_INSTANCE_ID) + .header("accept", MediaType.APPLICATION_JSON) + .then().expect() + .statusCode(Status.OK.getStatusCode()) + .contentType(ContentType.JSON) + .body("$.size()", equalTo(1)) + .when() + .get(PROCESS_INSTANCE_COMMENTS_URL); + + verifyTaskComments(mockTaskComments, response); + verify(taskServiceMock).getProcessInstanceComments(EXAMPLE_PROCESS_INSTANCE_ID); + } + + @Test + public void testGetProcessInstanceCommentsWithHistoryDisabled() { + mockHistoryDisabled(); + + given() + .pathParam("id", EXAMPLE_PROCESS_INSTANCE_ID) + .header("accept", MediaType.APPLICATION_JSON) + .then().expect() + .statusCode(Status.OK.getStatusCode()) + .contentType(ContentType.JSON) + .body("$.size()", equalTo(0)) + .when() + .get(PROCESS_INSTANCE_COMMENTS_URL); + } + @Test public void testGetFileVariable() { String variableKey = "aVariableKey"; @@ -4135,4 +4206,26 @@ protected void verifyBatchJson(String batchJson) { assertEquals(MockProvider.EXAMPLE_BATCH_JOB_DEFINITION_ID, batch.getBatchJobDefinitionId()); assertEquals(MockProvider.EXAMPLE_TENANT_ID, batch.getTenantId()); } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void verifyTaskComments(List mockTaskComments, Response response) { + List list = response.as(List.class); + assertEquals(1, list.size()); + + LinkedHashMap resourceHashMap = (LinkedHashMap) list.get(0); + + String returnedId = resourceHashMap.get("id"); + String returnedUserId = resourceHashMap.get("userId"); + String returnedTaskId = resourceHashMap.get("taskId"); + Date returnedTime = DateTimeUtil.parseDate(resourceHashMap.get("time")); + String returnedFullMessage = resourceHashMap.get("message"); + + Comment mockComment = mockTaskComments.get(0); + + assertEquals(mockComment.getId(), returnedId); + assertEquals(mockComment.getTaskId(), returnedTaskId); + assertEquals(mockComment.getUserId(), returnedUserId); + assertEquals(mockComment.getTime(), returnedTime); + assertEquals(mockComment.getFullMessage(), returnedFullMessage); + } }