/** * Copyright (c) 2015 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * 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.wso2.carbon.bpmn.rest.service.history; import org.activiti.engine.*; import org.activiti.engine.history.HistoricIdentityLink; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.history.HistoricProcessInstanceQuery; import org.activiti.engine.impl.HistoricProcessInstanceQueryProperty; import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity; import org.activiti.engine.impl.persistence.entity.VariableInstanceEntity; import org.activiti.engine.query.QueryProperty; import org.activiti.engine.task.Comment; import org.wso2.carbon.bpmn.rest.common.RequestUtil; import org.wso2.carbon.bpmn.rest.common.RestResponseFactory; import org.wso2.carbon.bpmn.rest.common.utils.BPMNOSGIService; import org.wso2.carbon.bpmn.rest.engine.variable.QueryVariable; import org.wso2.carbon.bpmn.rest.engine.variable.RestVariable; import org.wso2.carbon.bpmn.rest.model.common.DataResponse; import org.wso2.carbon.bpmn.rest.model.common.HistoricProcessInstanceQueryRequest; import org.wso2.carbon.bpmn.rest.model.history.HistoricIdentityLinkResponse; import org.wso2.carbon.bpmn.rest.model.history.HistoricIdentityLinkResponseCollection; import org.wso2.carbon.bpmn.rest.model.history.HistoricProcessInstancePaginateList; import org.wso2.carbon.bpmn.rest.model.history.HistoricProcessInstanceResponse; import org.wso2.carbon.bpmn.rest.model.runtime.CommentResponse; import org.wso2.carbon.bpmn.rest.model.runtime.CommentResponseCollection; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.*; @Path("/historic-process-instances") public class HistoricProcessInstanceService { protected static final List<String> allPropertiesList = new ArrayList<>(); private static Map<String, QueryProperty> allowedSortProperties = new HashMap<String, QueryProperty>(); @Context UriInfo uriInfo; static { allPropertiesList.add("processInstanceId"); allPropertiesList.add("processDefinitionKey"); allPropertiesList.add("processDefinitionId"); allPropertiesList.add("businessKey"); allPropertiesList.add("involvedUser"); allPropertiesList.add("finished"); allPropertiesList.add("superProcessInstanceId"); allPropertiesList.add("excludeSubprocesses"); allPropertiesList.add("finishedAfter"); allPropertiesList.add("finishedBefore"); allPropertiesList.add("startedAfter"); allPropertiesList.add("startedBefore"); allPropertiesList.add("startedBy"); allPropertiesList.add("includeProcessVariables"); allPropertiesList.add("tenantId"); allPropertiesList.add("tenantIdLike"); allPropertiesList.add("withoutTenantId"); allPropertiesList.add("start"); allPropertiesList.add("size"); allPropertiesList.add("order"); allPropertiesList.add("sort"); } static { allowedSortProperties.put("processInstanceId", HistoricProcessInstanceQueryProperty.PROCESS_INSTANCE_ID_); allowedSortProperties.put("processDefinitionId", HistoricProcessInstanceQueryProperty.PROCESS_DEFINITION_ID); allowedSortProperties.put("businessKey", HistoricProcessInstanceQueryProperty.BUSINESS_KEY); allowedSortProperties.put("startTime", HistoricProcessInstanceQueryProperty.START_TIME); allowedSortProperties.put("endTime", HistoricProcessInstanceQueryProperty.END_TIME); allowedSortProperties.put("duration", HistoricProcessInstanceQueryProperty.DURATION); allowedSortProperties.put("tenantId", HistoricProcessInstanceQueryProperty.TENANT_ID); } @GET @Path("/") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response getHistoricProcessInstances() { Map<String, String> allRequestParams = new HashMap<>(); for (String property:allPropertiesList){ String value= uriInfo.getQueryParameters().getFirst(property); if(value != null){ allRequestParams.put(property, value); } } // Populate query based on request HistoricProcessInstanceQueryRequest queryRequest = new HistoricProcessInstanceQueryRequest(); if (allRequestParams.get("processInstanceId") != null) { queryRequest.setProcessInstanceId(allRequestParams.get("processInstanceId")); } if (allRequestParams.get("processDefinitionKey") != null) { queryRequest.setProcessDefinitionKey(allRequestParams.get("processDefinitionKey")); } if (allRequestParams.get("processDefinitionId") != null) { queryRequest.setProcessDefinitionId(allRequestParams.get("processDefinitionId")); } if (allRequestParams.get("businessKey") != null) { queryRequest.setProcessBusinessKey(allRequestParams.get("businessKey")); } if (allRequestParams.get("involvedUser") != null) { queryRequest.setInvolvedUser(allRequestParams.get("involvedUser")); } if (allRequestParams.get("finished") != null) { queryRequest.setFinished(Boolean.valueOf(allRequestParams.get("finished"))); } if (allRequestParams.get("superProcessInstanceId") != null) { queryRequest.setSuperProcessInstanceId(allRequestParams.get("superProcessInstanceId")); } if (allRequestParams.get("excludeSubprocesses") != null) { queryRequest.setExcludeSubprocesses(Boolean.valueOf(allRequestParams.get("excludeSubprocesses"))); } if (allRequestParams.get("finishedAfter") != null) { queryRequest.setFinishedAfter(RequestUtil.getDate(allRequestParams, "finishedAfter")); } if (allRequestParams.get("finishedBefore") != null) { queryRequest.setFinishedBefore(RequestUtil.getDate(allRequestParams, "finishedBefore")); } if (allRequestParams.get("startedAfter") != null) { queryRequest.setStartedAfter(RequestUtil.getDate(allRequestParams, "startedAfter")); } if (allRequestParams.get("startedBefore") != null) { queryRequest.setStartedBefore(RequestUtil.getDate(allRequestParams, "startedBefore")); } if (allRequestParams.get("startedBy") != null) { queryRequest.setStartedBy(allRequestParams.get("startedBy")); } if (allRequestParams.get("includeProcessVariables") != null) { queryRequest.setIncludeProcessVariables(Boolean.valueOf(allRequestParams.get("includeProcessVariables"))); } if (allRequestParams.get("tenantId") != null) { queryRequest.setTenantId(allRequestParams.get("tenantId")); } if (allRequestParams.get("tenantIdLike") != null) { queryRequest.setTenantIdLike(allRequestParams.get("tenantIdLike")); } if (allRequestParams.get("withoutTenantId") != null) { queryRequest.setWithoutTenantId(Boolean.valueOf(allRequestParams.get("withoutTenantId"))); } return Response.ok().entity(getQueryResponse(queryRequest, allRequestParams)).build(); } @GET @Path("/{processInstanceId}") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response getProcessInstance(@PathParam("processInstanceId") String processInstanceId) { HistoricProcessInstanceResponse historicProcessInstanceResponse = new RestResponseFactory() .createHistoricProcessInstanceResponse(getHistoricProcessInstanceFromRequest(processInstanceId), uriInfo.getBaseUri().toString()); return Response.ok().entity(historicProcessInstanceResponse).build(); } @DELETE @Path("/{processInstanceId}") public Response deleteProcessInstance(@PathParam("processInstanceId") String processInstanceId) { HistoryService historyService = BPMNOSGIService.getHistoryService(); historyService.deleteHistoricProcessInstance(processInstanceId); return Response.ok().status(Response.Status.NO_CONTENT).build(); } @GET @Path("/{processInstanceId}/identitylinks") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response getProcessIdentityLinks(@PathParam("processInstanceId") String processInstanceId) { HistoryService historyService = BPMNOSGIService.getHistoryService(); List<HistoricIdentityLink> identityLinks = historyService.getHistoricIdentityLinksForProcessInstance(processInstanceId); if (identityLinks != null) { List<HistoricIdentityLinkResponse> historicIdentityLinkResponses = new RestResponseFactory() .createHistoricIdentityLinkResponseList(identityLinks, uriInfo.getBaseUri ().toString()); HistoricIdentityLinkResponseCollection historicIdentityLinkResponseCollection = new HistoricIdentityLinkResponseCollection(); historicIdentityLinkResponseCollection.setHistoricIdentityLinkResponses(historicIdentityLinkResponses); return Response.ok().entity(historicIdentityLinkResponseCollection).build(); } return Response.ok().build(); } @GET @Path("/{processInstanceId}/variables/{variableName}/data") public Response getVariableData(@PathParam("processInstanceId") String processInstanceId, @PathParam("variableName") String variableName) { try { Response.ResponseBuilder responseBuilder = Response.ok(); byte[] result = null; RestVariable variable = getVariableFromRequest(true, processInstanceId, variableName); if (RestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE.equals(variable.getType())) { result = (byte[]) variable.getValue(); responseBuilder.type("application/octet-stream"); } else if (RestResponseFactory.SERIALIZABLE_VARIABLE_TYPE.equals(variable.getType())) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(buffer); outputStream.writeObject(variable.getValue()); outputStream.close(); result = buffer.toByteArray(); responseBuilder.type("application/x-java-serialized-object"); } else { throw new ActivitiObjectNotFoundException("The variable does not have a binary data stream.", null); } return responseBuilder.entity(result).build(); } catch(IOException ioe) { // Re-throw IOException throw new ActivitiException("Unexpected exception getting variable data", ioe); } } @GET @Path("/{processInstanceId}/comments") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response getComments(@PathParam("processInstanceId") String processInstanceId) { TaskService taskService = BPMNOSGIService.getTaskService(); HistoricProcessInstance instance = getHistoricProcessInstanceFromRequest(processInstanceId); List<CommentResponse> commentResponseList = new RestResponseFactory().createRestCommentList(taskService .getProcessInstanceComments(instance.getId()), uriInfo.getBaseUri().toString()); CommentResponseCollection commentResponseCollection = new CommentResponseCollection(); commentResponseCollection.setCommentResponseList(commentResponseList); return Response.ok().entity(commentResponseCollection).build(); } @POST @Path("/{processInstanceId}/comments") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response createComment(@PathParam("processInstanceId") String processInstanceId, CommentResponse comment) { HistoricProcessInstance instance = getHistoricProcessInstanceFromRequest(processInstanceId); if (comment.getMessage() == null) { throw new ActivitiIllegalArgumentException("Comment text is required."); } TaskService taskService = BPMNOSGIService.getTaskService(); Comment createdComment = taskService.addComment(null, instance.getId(), comment.getMessage()); CommentResponse commentResponse = new RestResponseFactory().createRestComment(createdComment, uriInfo .getBaseUri().toString()); return Response.ok().status(Response.Status.CREATED).entity(commentResponse).build(); } @GET @Path("/{processInstanceId}/comments/{commentId}") @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response getComment(@PathParam("processInstanceId") String processInstanceId, @PathParam("commentId") String commentId) { HistoricProcessInstance instance = getHistoricProcessInstanceFromRequest(processInstanceId); TaskService taskService = BPMNOSGIService.getTaskService(); Comment comment = taskService.getComment(commentId); if (comment == null || comment.getProcessInstanceId() == null || !comment.getProcessInstanceId().equals(instance.getId())) { throw new ActivitiObjectNotFoundException("Process instance '" + instance.getId() + "' doesn't have a comment with id '" + commentId + "'.", Comment.class); } CommentResponse commentResponse = new RestResponseFactory().createRestComment(comment, uriInfo.getBaseUri().toString()); return Response.ok().entity(commentResponse).build(); } @DELETE @Path("/{processInstanceId}/comments/{commentId}") public Response deleteComment(@PathParam("processInstanceId") String processInstanceId, @PathParam("commentId") String commentId) { TaskService taskService = BPMNOSGIService.getTaskService(); HistoricProcessInstance instance = getHistoricProcessInstanceFromRequest(processInstanceId); Comment comment = taskService.getComment(commentId); if (comment == null || comment.getProcessInstanceId() == null || !comment.getProcessInstanceId().equals(instance.getId())) { throw new ActivitiObjectNotFoundException("Process instance '" + instance.getId() + "' doesn't have a comment with id '" + commentId + "'.", Comment.class); } taskService.deleteComment(commentId); return Response.ok().status(Response.Status.NO_CONTENT).build(); } public RestVariable getVariableFromRequest(boolean includeBinary, String processInstanceId, String variableName) { HistoryService historyService = BPMNOSGIService.getHistoryService(); HistoricProcessInstance processObject = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId).includeProcessVariables().singleResult(); if (processObject == null) { throw new ActivitiObjectNotFoundException("Historic process instance '" + processInstanceId + "' couldn't be found.", HistoricProcessInstanceEntity.class); } Object value = processObject.getProcessVariables().get(variableName); if (value == null) { throw new ActivitiObjectNotFoundException("Historic process instance '" + processInstanceId + "' variable value for " + variableName + " couldn't be found.", VariableInstanceEntity.class); } else { return new RestResponseFactory().createRestVariable(variableName, value, null, processInstanceId, RestResponseFactory.VARIABLE_HISTORY_PROCESS, includeBinary, uriInfo.getBaseUri().toString()); } } protected DataResponse getQueryResponse(HistoricProcessInstanceQueryRequest queryRequest, Map<String, String> allRequestParams) { HistoryService historyService = BPMNOSGIService.getHistoryService(); HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); // Populate query based on request if (queryRequest.getProcessInstanceId() != null) { query.processInstanceId(queryRequest.getProcessInstanceId()); } if (queryRequest.getProcessInstanceIds() != null && !queryRequest.getProcessInstanceIds().isEmpty()) { query.processInstanceIds(new HashSet<String>(queryRequest.getProcessInstanceIds())); } if (queryRequest.getProcessDefinitionKey() != null) { query.processDefinitionKey(queryRequest.getProcessDefinitionKey()); } if (queryRequest.getProcessDefinitionId() != null) { query.processDefinitionId(queryRequest.getProcessDefinitionId()); } if (queryRequest.getProcessBusinessKey() != null) { query.processInstanceBusinessKey(queryRequest.getProcessBusinessKey()); } if (queryRequest.getInvolvedUser() != null) { query.involvedUser(queryRequest.getInvolvedUser()); } if (queryRequest.getSuperProcessInstanceId() != null) { query.superProcessInstanceId(queryRequest.getSuperProcessInstanceId()); } if (queryRequest.getExcludeSubprocesses() != null) { query.excludeSubprocesses(queryRequest.getExcludeSubprocesses()); } if (queryRequest.getFinishedAfter() != null) { query.finishedAfter(queryRequest.getFinishedAfter()); } if (queryRequest.getFinishedBefore() != null) { query.finishedBefore(queryRequest.getFinishedBefore()); } if (queryRequest.getStartedAfter() != null) { query.startedAfter(queryRequest.getStartedAfter()); } if (queryRequest.getStartedBefore() != null) { query.startedBefore(queryRequest.getStartedBefore()); } if (queryRequest.getStartedBy() != null) { query.startedBy(queryRequest.getStartedBy()); } if (queryRequest.getFinished() != null) { if (queryRequest.getFinished()) { query.finished(); } else { query.unfinished(); } } if (queryRequest.getIncludeProcessVariables() != null) { if (queryRequest.getIncludeProcessVariables()) { query.includeProcessVariables(); } } if (queryRequest.getVariables() != null) { addVariables(query, queryRequest.getVariables()); } if (queryRequest.getTenantId() != null) { query.processInstanceTenantId(queryRequest.getTenantId()); } if (queryRequest.getTenantIdLike() != null) { query.processInstanceTenantIdLike(queryRequest.getTenantIdLike()); } if (Boolean.TRUE.equals(queryRequest.getWithoutTenantId())) { query.processInstanceWithoutTenantId(); } RestResponseFactory restResponseFactory = new RestResponseFactory(); DataResponse dataResponse = new HistoricProcessInstancePaginateList(restResponseFactory, uriInfo).paginateList( allRequestParams, queryRequest, query, "processInstanceId", allowedSortProperties); return dataResponse; } protected void addVariables(HistoricProcessInstanceQuery processInstanceQuery, List<QueryVariable> variables) { for (QueryVariable variable : variables) { if (variable.getVariableOperation() == null) { throw new ActivitiIllegalArgumentException("Variable operation is missing for variable: " + variable.getName()); } if (variable.getValue() == null) { throw new ActivitiIllegalArgumentException("Variable value is missing for variable: " + variable.getName()); } boolean nameLess = variable.getName() == null; RestResponseFactory restResponseFactory = new RestResponseFactory(); Object actualValue = restResponseFactory.getVariableValue(variable); // A value-only query is only possible using equals-operator if (nameLess && variable.getVariableOperation() != QueryVariable.QueryVariableOperation.EQUALS) { throw new ActivitiIllegalArgumentException("Value-only query (without a variable-name) is only supported when using 'equals' operation."); } switch (variable.getVariableOperation()) { case EQUALS: if (nameLess) { processInstanceQuery.variableValueEquals(actualValue); } else { processInstanceQuery.variableValueEquals(variable.getName(), actualValue); } break; case EQUALS_IGNORE_CASE: if (actualValue instanceof String) { processInstanceQuery.variableValueEqualsIgnoreCase(variable.getName(), (String) actualValue); } else { throw new ActivitiIllegalArgumentException("Only string variable values are supported when ignoring casing, but was: " + actualValue.getClass().getName()); } break; case NOT_EQUALS: processInstanceQuery.variableValueNotEquals(variable.getName(), actualValue); break; case LIKE: if (actualValue instanceof String) { processInstanceQuery.variableValueLike(variable.getName(), (String) actualValue); } else { throw new ActivitiIllegalArgumentException("Only string variable values are supported for like, but was: " + actualValue.getClass().getName()); } break; case GREATER_THAN: processInstanceQuery.variableValueGreaterThan(variable.getName(), actualValue); break; case GREATER_THAN_OR_EQUALS: processInstanceQuery.variableValueGreaterThanOrEqual(variable.getName(), actualValue); break; case LESS_THAN: processInstanceQuery.variableValueLessThan(variable.getName(), actualValue); break; case LESS_THAN_OR_EQUALS: processInstanceQuery.variableValueLessThanOrEqual(variable.getName(), actualValue); break; default: throw new ActivitiIllegalArgumentException("Unsupported variable query operation: " + variable.getVariableOperation()); } } } protected HistoricProcessInstance getHistoricProcessInstanceFromRequest(String processInstanceId) { HistoryService historyService = BPMNOSGIService.getHistoryService(); HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId).singleResult(); if (processInstance == null) { throw new ActivitiObjectNotFoundException("Could not find a process instance with id '" + processInstanceId + "'.", HistoricProcessInstance.class); } return processInstance; } }