2 * ============LICENSE_START=======================================================
\r
4 * ================================================================================
\r
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * ================================================================================
\r
7 * Modifications Copyright (c) 2019 Samsung
\r
8 * ================================================================================
\r
9 * Licensed under the Apache License, Version 2.0 (the "License");
\r
10 * you may not use this file except in compliance with the License.
\r
11 * You may obtain a copy of the License at
\r
13 * http://www.apache.org/licenses/LICENSE-2.0
\r
15 * Unless required by applicable law or agreed to in writing, software
\r
16 * distributed under the License is distributed on an "AS IS" BASIS,
\r
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
18 * See the License for the specific language governing permissions and
\r
19 * limitations under the License.
\r
20 * ============LICENSE_END=========================================================
\r
23 package org.oran.otf.camunda.workflow;
\r
25 import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;
\r
26 import org.oran.otf.camunda.exception.TestExecutionException;
\r
27 import org.oran.otf.camunda.exception.WorkflowProcessorException;
\r
28 import org.oran.otf.camunda.model.ExecutionConstants.ExecutionVariable;
\r
29 import org.oran.otf.camunda.model.ExecutionConstants.TestResult;
\r
30 import org.oran.otf.camunda.model.WorkflowResponse;
\r
31 import org.oran.otf.camunda.service.ProcessEngineAwareService;
\r
32 import org.oran.otf.camunda.workflow.utility.WorkflowUtility;
\r
33 import org.oran.otf.common.model.*;
\r
34 import org.oran.otf.common.model.historic.TestDefinitionHistoric;
\r
35 import org.oran.otf.common.model.historic.TestInstanceHistoric;
\r
36 import org.oran.otf.common.model.local.BpmnInstance;
\r
37 import org.oran.otf.common.model.local.ParallelFlowInput;
\r
38 import org.oran.otf.common.repository.*;
\r
39 import org.oran.otf.common.utility.Utility;
\r
40 import org.oran.otf.common.utility.database.Generic;
\r
41 import org.oran.otf.common.utility.permissions.PermissionChecker;
\r
42 import org.oran.otf.common.utility.permissions.UserPermission;
\r
43 import com.mongodb.client.result.UpdateResult;
\r
44 import java.util.ArrayList;
\r
45 import java.util.Date;
\r
46 import java.util.HashMap;
\r
47 import java.util.List;
\r
48 import java.util.Map;
\r
49 import java.util.Objects;
\r
50 import java.util.UUID;
\r
51 import org.bson.types.ObjectId;
\r
52 import org.camunda.bpm.BpmPlatform;
\r
53 import org.camunda.bpm.engine.RepositoryService;
\r
54 import org.camunda.bpm.engine.RuntimeService;
\r
55 import org.camunda.bpm.engine.repository.ProcessDefinition;
\r
56 import org.camunda.bpm.engine.runtime.ProcessInstance;
\r
57 import org.camunda.bpm.engine.variable.VariableMap;
\r
58 import org.camunda.bpm.engine.variable.Variables;
\r
59 import org.camunda.bpm.engine.variable.impl.VariableMapImpl;
\r
60 import org.oran.otf.common.model.*;
\r
61 import org.oran.otf.common.repository.*;
\r
62 import org.slf4j.Logger;
\r
63 import org.slf4j.LoggerFactory;
\r
64 import org.springframework.beans.factory.annotation.Autowired;
\r
65 import org.springframework.boot.context.event.ApplicationReadyEvent;
\r
66 import org.springframework.context.event.EventListener;
\r
67 import org.springframework.data.mongodb.core.MongoTemplate;
\r
68 import org.springframework.data.mongodb.core.query.Criteria;
\r
69 import org.springframework.data.mongodb.core.query.Query;
\r
70 import org.springframework.data.mongodb.core.query.Update;
\r
71 import org.springframework.stereotype.Component;
\r
74 public class WorkflowProcessor extends ProcessEngineAwareService {
\r
76 private static final String logPrefix = Utility.getLoggerPrefix();
\r
77 private static final Logger logger = LoggerFactory.getLogger(WorkflowProcessor.class);
\r
80 GroupRepository groupRepository;
\r
82 TestDefinitionRepository testDefinitionRepository;
\r
84 TestInstanceRepository testInstanceRepository;
\r
86 UserRepository userRepository;
\r
88 TestExecutionRepository testExecutionRepository;
\r
90 MongoTemplate mongoOperation;
\r
92 WorkflowUtility workflowUtility;
\r
94 private RuntimeService runtimeService;
\r
95 private RepositoryService repositoryService;
\r
97 // Note: the business key is used to identify the process in unit tests
\r
98 protected static String getBusinessKey(Map<String, Object> inputVariables) {
\r
99 return getOrCreate(inputVariables, "otf-business-key");
\r
102 protected static Map<String, Object> getInputVariables(VariableMapImpl variableMap) {
\r
103 Map<String, Object> inputVariables = new HashMap<>();
\r
104 @SuppressWarnings("unchecked")
\r
105 Map<String, Object> vMap = (Map<String, Object>) variableMap.get("variables");
\r
106 for (Map.Entry<String, Object> entry : vMap.entrySet()) {
\r
107 String vName = entry.getKey();
\r
108 Object value = entry.getValue();
\r
109 @SuppressWarnings("unchecked")
\r
110 Map<String, Object> valueMap = (Map<String, Object>) value; // value, type
\r
111 inputVariables.put(vName, valueMap.get("value"));
\r
113 return inputVariables;
\r
116 protected static String getOrCreate(Map<String, Object> inputVariables, String key) {
\r
117 String value = Objects.toString(inputVariables.get(key), null);
\r
118 if (value == null) {
\r
119 value = UUID.randomUUID().toString();
\r
120 inputVariables.put(key, value);
\r
125 private static void buildVariable(
\r
126 String key, String value, Map<String, Object> variableValueType) {
\r
127 Map<String, Object> host = new HashMap<>();
\r
128 host.put("value", value);
\r
129 host.put("type", "String");
\r
130 variableValueType.put(key, host);
\r
133 @EventListener(ApplicationReadyEvent.class)
\r
134 private void initialize() {
\r
135 if (this.runtimeService == null) {
\r
136 this.runtimeService =
\r
137 BpmPlatform.getProcessEngineService()
\r
138 .getProcessEngine(OtfCamundaConfiguration.processEngineName)
\r
139 .getRuntimeService();
\r
141 if (this.repositoryService == null) {
\r
142 this.repositoryService =
\r
143 BpmPlatform.getProcessEngineService()
\r
144 .getProcessEngine(OtfCamundaConfiguration.processEngineName)
\r
145 .getRepositoryService();
\r
149 public TestExecution processWorkflowRequest(WorkflowRequest request)
\r
150 throws WorkflowProcessorException {
\r
152 // Check if the test instance exists.
\r
153 TestInstance testInstance =
\r
154 Generic.findByIdGeneric(testInstanceRepository, request.getTestInstanceId());
\r
155 if (testInstance == null) {
\r
156 WorkflowResponse response = new WorkflowResponse();
\r
157 response.setMessage(
\r
159 "Test instance with identifier %s was not found.",
\r
160 request.getTestInstanceId().toString()));
\r
161 response.setMessageCode(404);
\r
162 response.setResponse("Unable to start the test instance.");
\r
163 TestExecution testExecution = generateTestExecution(request, null, null, null);
\r
164 testExecution.setTestResult(TestResult.DOES_NOT_EXIST);
\r
165 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
166 response.setTestExecution(testExecution);
\r
167 throw new WorkflowProcessorException(response);
\r
170 // Override the test data and vth input of the instance if the request contains the data.
\r
171 Map<String, Object> vthInput =
\r
172 request.getVthInput() == null ? testInstance.getVthInput() : request.getVthInput();
\r
173 Map<String, Object> testData =
\r
174 request.getTestData() == null ? testInstance.getTestData() : request.getTestData();
\r
175 Map<String, ParallelFlowInput> plfoInput =
\r
176 request.getPfloInput() == null ? testInstance.getPfloInput() : request.getPfloInput();
\r
178 testInstance.setVthInput((HashMap<String, Object>) vthInput);
\r
179 testInstance.setTestData((HashMap<String, Object>) testData);
\r
180 testInstance.setPfloInput((HashMap<String, ParallelFlowInput>) plfoInput);
\r
183 // Check if the test definition linked to the test instance is also present.
\r
184 TestDefinition testDefinition =
\r
185 Generic.findByIdGeneric(testDefinitionRepository, testInstance.getTestDefinitionId());
\r
186 if (testDefinition == null) {
\r
187 WorkflowResponse response = new WorkflowResponse();
\r
188 response.setMessage(
\r
190 "Test definition with identifier %s was not found.",
\r
191 testInstance.getTestDefinitionId().toString()));
\r
192 response.setMessageCode(404);
\r
193 response.setResponse("Unable to start the test instance.");
\r
194 TestExecution testExecution = generateTestExecution(request, testInstance, null, null);
\r
195 testExecution.setTestResult(TestResult.DOES_NOT_EXIST);
\r
196 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
197 response.setTestExecution(testExecution);
\r
198 throw new WorkflowProcessorException(response);
\r
201 // is using latest defintion, verify that the processDefinitionId within camunda is present in
\r
202 // the test definition bpmn instance list
\r
203 if (testInstance.isUseLatestTestDefinition()) {
\r
204 String processDefinitionId =
\r
205 findLatestProcessDefinition(testDefinition.getProcessDefinitionKey());
\r
206 boolean isBpmnInstancePresent =
\r
207 verifyIdExistsInTestDefinition(testDefinition, processDefinitionId);
\r
208 if (isBpmnInstancePresent) {
\r
209 testInstance.setProcessDefinitionId(processDefinitionId);
\r
211 WorkflowResponse response = new WorkflowResponse();
\r
212 response.setMessage(
\r
214 "Latest Test Definition does not exist for key %s.",
\r
215 testDefinition.getProcessDefinitionKey()));
\r
216 response.setMessageCode(404);
\r
217 response.setResponse("Unable to start the test instance.");
\r
218 TestExecution testExecution =
\r
219 generateTestExecution(request, testInstance, testDefinition, null);
\r
220 testExecution.setTestResult(TestResult.DOES_NOT_EXIST);
\r
221 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
222 response.setTestExecution(testExecution);
\r
223 throw new WorkflowProcessorException(response);
\r
227 // Check if the entity making the request has permission to run the test instance.
\r
228 User executor = Generic.findByIdGeneric(userRepository, request.getExecutorId());
\r
229 if (executor == null) {
\r
230 WorkflowResponse response = new WorkflowResponse();
\r
231 response.setMessage(
\r
233 .format("User with id %s was not found.", request.getExecutorId().toString()));
\r
234 response.setMessageCode(404);
\r
235 response.setResponse("Unable to start the test instance.");
\r
236 TestExecution testExecution =
\r
237 generateTestExecution(request, testInstance, testDefinition, null);
\r
238 testExecution.setTestResult(TestResult.DOES_NOT_EXIST);
\r
239 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
240 response.setTestExecution(testExecution);
\r
241 throw new WorkflowProcessorException(response);
\r
243 // if (!workflowUtility.hasPermission(executor, testInstance)) {
\r
244 // WorkflowResponse response = new WorkflowResponse();
\r
245 // response.setMessage(
\r
247 // "The user with email %s does not have permission to execute test instance with id: %s.",
\r
248 // executor.getEmail(), testInstance.get_id().toString()));
\r
249 // response.setMessageCode(401);
\r
250 // response.setResponse("Unauthorized to execute the test instance.");
\r
251 // TestExecution testExecution =
\r
252 // generateTestExecution(request, testInstance, testDefinition, executor);
\r
253 // testExecution.setTestResult(TestResult.UNAUTHORIZED);
\r
254 // testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
255 // response.setTestExecution(testExecution);
\r
256 // throw new WorkflowProcessorException(response);
\r
258 Group testInstanceGroup = groupRepository.findById(testInstance.getGroupId().toString()).orElse(null);
\r
259 if(testInstanceGroup == null){
\r
260 WorkflowResponse response = new WorkflowResponse();
\r
261 response.setMessage(
\r
262 String.format("unable to find test instance group. Group id: %s",testInstance.getGroupId().toString()));
\r
263 response.setMessageCode(404);
\r
264 response.setResponse("unable to find test instance group");
\r
265 TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);
\r
266 testExecution.setTestResult(TestResult.DOES_NOT_EXIST);
\r
267 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
268 response.setTestExecution(testExecution);
\r
269 throw new WorkflowProcessorException(response);
\r
271 if (!PermissionChecker.hasPermissionTo(executor,testInstanceGroup, UserPermission.Permission.EXECUTE,groupRepository)){
\r
272 WorkflowResponse response = new WorkflowResponse();
\r
273 response.setMessage(
\r
275 "User with email: %s does not have execute permission on test instance group with id: %s",
\r
276 executor.getEmail(),testInstance.getGroupId().toString()));
\r
277 response.setMessageCode(401);
\r
278 response.setResponse("unauthorized to execute test instance");
\r
279 TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);
\r
280 testExecution.setTestResult(TestResult.UNAUTHORIZED);
\r
281 testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));
\r
282 response.setTestExecution(testExecution);
\r
283 throw new WorkflowProcessorException(response);
\r
286 // Generate a testExecution with a historic copy of the test instance, test definition, and the
\r
287 // email of the person executing the test.
\r
288 TestExecution testExecution =
\r
289 generateTestExecution(request, testInstance, testDefinition, executor);
\r
291 // Prepare the test details, test result, test execution, and vth input variables for the
\r
292 // process instance.
\r
293 VariableMap variableMap =
\r
294 Variables.createVariables()
\r
296 ExecutionVariable.TEST_DETAILS,
\r
297 Variables.objectValue(testExecution.getTestDetails()).create())
\r
299 ExecutionVariable.TEST_RESULT,
\r
300 Variables.objectValue(testExecution.getTestResult()).create())
\r
302 ExecutionVariable.TEST_RESULT_MESSAGE,
\r
303 Variables.objectValue(testExecution.getTestResultMessage()).create())
\r
304 .putValueTyped(ExecutionVariable.VTH_INPUT,
\r
305 Variables.objectValue(vthInput).create())
\r
306 .putValueTyped(ExecutionVariable.TEST_DATA,
\r
307 Variables.objectValue(testData).create())
\r
309 ExecutionVariable.TEST_EXECUTION,
\r
310 Variables.objectValue(testExecution)
\r
311 .serializationDataFormat(Variables.SerializationDataFormats.JAVA)
\r
314 ExecutionVariable.PFLO_INPUT,
\r
315 Variables.objectValue(plfoInput)
\r
316 .serializationDataFormat(Variables.SerializationDataFormats.JAVA)
\r
319 if (testInstance.isUseLatestTestDefinition()) {
\r
320 return startProcessByKey(
\r
321 testDefinition.getProcessDefinitionKey(), variableMap, testExecution);
\r
323 return startProcessById(testInstance.getProcessDefinitionId(), variableMap,
\r
328 public TestExecution startProcessByKey(
\r
329 String processKey, Map<String, Object> variableMap, TestExecution testExecution) {
\r
332 "***OTF startProcessInstanceByKey with processKey: {} and variables: {}",
\r
336 // Set the start time as close to the runtime service start function.
\r
337 testExecution.setStartTime(new Date(System.currentTimeMillis()));
\r
338 testExecutionRepository.insert(testExecution);
\r
340 ProcessInstance processInstance =
\r
341 runtimeService.startProcessInstanceByKey(
\r
342 processKey, testExecution.getBusinessKey(), variableMap);
\r
344 // Update the test execution object with the processInstanceId after the processInstanceId is
\r
346 testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());
\r
347 Query query = new Query();
\r
348 query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));
\r
349 // Also add businessKey as a criteria because the object won't be found if the business key
\r
350 // was somehow modified in the workflow.
\r
351 query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));
\r
352 Update update = new Update();
\r
353 update.set("processInstanceId", processInstance.getProcessInstanceId());
\r
354 UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);
\r
355 // Check the status of the findAndUpdate database, and appropriately handle the errors.
\r
356 if (result.getMatchedCount() == 0) {
\r
357 throw new TestExecutionException(
\r
359 "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",
\r
360 testExecution.get_id(), testExecution.getBusinessKey()));
\r
361 } else if (result.getModifiedCount() == 0) {
\r
362 throw new TestExecutionException(
\r
363 "Unable to persist the testExecution to the database.");
\r
371 + processInstance.getProcessInstanceId()
\r
373 + (processInstance.isEnded() ? "ENDED" : "RUNNING"));
\r
374 } catch (Exception e) {
\r
375 WorkflowResponse workflowResponse = new WorkflowResponse();
\r
376 workflowResponse.setResponse("Error occurred while executing the process: " + e);
\r
377 workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());
\r
378 workflowResponse.setMessageCode(500);
\r
379 workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());
\r
380 testExecution.setTestResult(TestResult.FAILED);
\r
382 .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));
\r
383 workflowResponse.setTestExecution(testExecution);
\r
384 throw new WorkflowProcessorException(workflowResponse);
\r
387 return testExecution;
\r
390 private TestExecution startProcessById(
\r
391 String processId, Map<String, Object> variableMap, TestExecution testExecution) {
\r
394 "***OTF startProcessInstanceById with processId: {} and variables: {}",
\r
398 // Set the start time as close to the runtime service start function.
\r
399 testExecution.setStartTime(new Date(System.currentTimeMillis()));
\r
400 testExecutionRepository.insert(testExecution);
\r
402 ProcessInstance processInstance =
\r
403 runtimeService.startProcessInstanceById(
\r
404 processId, testExecution.getBusinessKey(), variableMap);
\r
406 // Update the test execution object with the processInstanceId after the processInstanceId is
\r
408 testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());
\r
409 Query query = new Query();
\r
410 query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));
\r
411 // Also add businessKey as a criteria because the object won't be found if the business key
\r
412 // was somehow modified in the workflow.
\r
413 query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));
\r
414 Update update = new Update();
\r
415 update.set("processInstanceId", processInstance.getProcessInstanceId());
\r
416 UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);
\r
417 // Check the status of the findAndUpdate database, and appropriately handle the errors.
\r
418 if (result.getMatchedCount() == 0) {
\r
419 throw new TestExecutionException(
\r
421 "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",
\r
422 testExecution.get_id(), testExecution.getBusinessKey()));
\r
423 } else if (result.getModifiedCount() == 0) {
\r
424 throw new TestExecutionException(
\r
425 "Unable to persist the testExecution to the database.");
\r
431 + processInstance.getProcessInstanceId()
\r
433 + processInstance.getProcessInstanceId()
\r
435 + (processInstance.isEnded() ? "ENDED" : "RUNNING"));
\r
436 } catch (Exception e) {
\r
437 WorkflowResponse workflowResponse = new WorkflowResponse();
\r
438 workflowResponse.setResponse("Error occurred while executing the process: " + e);
\r
439 workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());
\r
440 workflowResponse.setMessageCode(500);
\r
441 workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());
\r
442 testExecution.setTestResult(TestResult.FAILED);
\r
444 .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));
\r
445 workflowResponse.setTestExecution(testExecution);
\r
446 throw new WorkflowProcessorException(workflowResponse);
\r
449 return testExecution;
\r
452 private TestExecution generateTestExecution(
\r
453 WorkflowRequest request,
\r
454 TestInstance testInstance,
\r
455 TestDefinition testDefinition,
\r
457 TestExecution testExecution = new TestExecution();
\r
458 testExecution.set_id(new ObjectId());
\r
459 testExecution.setExecutorId(request.getExecutorId());
\r
460 testExecution.setAsync(request.isAsync());
\r
461 testExecution.setStartTime(null);
\r
462 testExecution.setTestDetails(new HashMap<>());
\r
463 testExecution.setTestResult(TestResult.UNKNOWN);
\r
464 testExecution.setTestResultMessage("");
\r
465 testExecution.setProcessInstanceId(null);
\r
466 testExecution.setBusinessKey(UUID.randomUUID().toString());
\r
467 testExecution.setTestHeadResults(new ArrayList<>());
\r
468 testExecution.setTestInstanceResults(new ArrayList<>());
\r
469 if (testInstance != null) {
\r
470 testExecution.setGroupId(testInstance.getGroupId());
\r
471 TestInstanceHistoric testInstanceHistoric = new TestInstanceHistoric(testInstance);
\r
472 testExecution.setHistoricTestInstance(testInstanceHistoric);
\r
474 if (testDefinition != null && testInstance != null) {
\r
475 TestDefinitionHistoric testDefinitionHistoric =
\r
476 new TestDefinitionHistoric(testDefinition, testInstance.getProcessDefinitionId());
\r
477 testExecution.setHistoricTestDefinition(testDefinitionHistoric);
\r
479 if (executor != null) {
\r
480 testExecution.setHistoricEmail(executor.getEmail());
\r
482 return testExecution;
\r
485 private Map<String, Object> generateTestDetailsWithMessage(String message) {
\r
486 Map<String, Object> map = new HashMap<>();
\r
487 map.put("message", message);
\r
491 private String findLatestProcessDefinition(String processDefinitionKey) {
\r
492 logger.info("Before find process definition key query.");
\r
493 ProcessDefinition definition =
\r
495 .createProcessDefinitionQuery()
\r
496 .processDefinitionKey(processDefinitionKey)
\r
499 logger.info("After find process definition key query.");
\r
500 String processDefinitionId = null;
\r
501 if (definition != null) {
\r
502 processDefinitionId = definition.getId();
\r
504 return processDefinitionId;
\r
507 private boolean verifyIdExistsInTestDefinition(
\r
508 TestDefinition definition, String processDefinitionId) {
\r
509 if (processDefinitionId == null || definition == null) {
\r
513 List<BpmnInstance> bpmnInstances = definition.getBpmnInstances();
\r
514 BpmnInstance bpmnInstance =
\r
515 bpmnInstances.stream()
\r
518 return _bpmnInstance.isDeployed()
\r
519 && _bpmnInstance.getProcessDefinitionId() != null
\r
520 && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId);
\r
524 return bpmnInstance != null;
\r