--- /dev/null
+/* Copyright (c) 2019 AT&T Intellectual Property. #\r
+# #\r
+# Licensed under the Apache License, Version 2.0 (the "License"); #\r
+# you may not use this file except in compliance with the License. #\r
+# You may obtain a copy of the License at #\r
+# #\r
+# http://www.apache.org/licenses/LICENSE-2.0 #\r
+# #\r
+# Unless required by applicable law or agreed to in writing, software #\r
+# distributed under the License is distributed on an "AS IS" BASIS, #\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #\r
+# See the License for the specific language governing permissions and #\r
+# limitations under the License. #\r
+##############################################################################*/\r
+\r
+\r
+const { parseString, Builder } = require('xml2js');\r
+const logger = require('../../../lib/logger');\r
+const Response = require('http-response-object');\r
+\r
+class Bpmn {\r
+ constructor(app, data, params){\r
+ this.delegates = [\r
+ {\r
+ name: 'vth',\r
+ regex: new RegExp('^(vth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
+ delegate: '${callTestHeadDelegate}'\r
+ },\r
+ {\r
+ name: 'lvth',\r
+ regex: new RegExp('^(lvth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
+ topic: 'vth'\r
+ },\r
+ {\r
+ name: 'log',\r
+ regex: new RegExp('^UTIL:LogTestResult$', 'i'),\r
+ delegate: '${logTestResultDelegate}',\r
+ },\r
+ {\r
+ name: 'pflo',\r
+ regex: new RegExp('^PFLO(:(.+))?$', 'i'),\r
+ delegate: '${runTestInstanceDelegate}',\r
+ topic: 'testInstance'\r
+ },\r
+ {\r
+ name: 'SubFlow',\r
+ regex: new RegExp('^SUBFLOW(:(.+))?$', 'i'),\r
+ delegate: '${runTestInstanceDelegate}',\r
+ topic: 'testInstance'\r
+ },\r
+ {\r
+ name: 'dmaap',\r
+ regex: new RegExp('^PostResultsToDMaaP(:(.+))?$', 'i'),\r
+ delegate: '${postResultsToDMaaPDelegate}'\r
+ },\r
+ {\r
+ name: 'mail',\r
+ regex: new RegExp('^UTIL:SendMail(:(.+))?$', 'i'),\r
+ delegate: '${sendMailDelegate}'\r
+ }\r
+ ];\r
+\r
+ this.serviceTasksNotAllowed = [\r
+ {\r
+ key: 'camunda:class'\r
+ }\r
+ ]\r
+ \r
+ this.params = params;\r
+ this.data = data;\r
+ this.app = app;\r
+ this.parsedXMLtoJSON = null;\r
+ this.bpmnVthTaskIds = [];\r
+ this.bpmnPfloTaskIds = [];\r
+ this.processDefinitionKey = null;\r
+ this.errors = {};\r
+ this.hasLog = false; //1 log is required in each workflow\r
+ this.hasTestHeads = false;\r
+ \r
+ }\r
+\r
+ async validate(){\r
+ //convert bpmn to json\r
+ //console.log(this.data.testDefinition);\r
+ parseString(\r
+ this.data.testDefinition.bpmnInstances[this.data.testDefinition.currentVersion].bpmnXml,\r
+ (err, result) => {\r
+ if (err) {\r
+ logger.error(err);\r
+ }\r
+ this.parsedXMLtoJSON = Object.assign({}, result);\r
+ }\r
+ );\r
+\r
+ //If the bpmn was unable to be parsed, return error response\r
+ if (!this.parsedXMLtoJSON) {\r
+ return new Response(500, {}, { errors: { parsingError: { error: 'Failed to parse bpmn. Try Again.' } } });\r
+ }\r
+\r
+ //set temp process\r
+ var process = this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0];\r
+\r
+\r
+ // Not needed with new modeler\r
+ //If a process definition key was sent with the requrest, use it instead\r
+ if (this.data.testDefinition.processDefinitionKey && this.data.testDefinition.processDefinitionKey != '') {\r
+ this.processDefinitionKey = this.data.testDefinition.processDefinitionKey;\r
+ }else{\r
+ this.processDefinitionKey = process.$.id;\r
+ }\r
+ \r
+ //Check to see if the process definition key is unique\r
+ let key = await this.app.services[this.app.get('base-path') + 'bpmn-validate'].get(this.processDefinitionKey, this.params).then();\r
+ if(key.statusCode != 200 && key.errors && key.errors.processDefinitionKey){\r
+ this.errors.processDefinitionKey = {\r
+ error: 'Process Definition Key has already been used',\r
+ key: this.processDefinitionKey\r
+ };\r
+ }\r
+ \r
+ // Verify start task(s) are async. Only start task(s) in main process\r
+ if (process['bpmn:startEvent']) {\r
+ for (var j = 0; j < process['bpmn:startEvent'].length; j++) {\r
+ var startEvent = process['bpmn:startEvent'][j];\r
+ if (startEvent.$['camunda:asyncBefore'] != 'true') {\r
+ this.errors.startEvent = { error: 'Start Event, ' + startEvent.$.id + ', is not async' };\r
+ }\r
+ }\r
+ } else {\r
+ this.errors.startEvent = { error: 'Workflow must have a start even' };\r
+ }\r
+ \r
+ //Find all of the task boxes that need to be changed (recursive)\r
+ await this.findTasks(this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0]);\r
+ \r
+ // If log task did not exist, log\r
+ if (!this.hasLog) {\r
+ this.errors.required = { error: 'No LogSetResult task. One is required.' };\r
+ }\r
+ \r
+ // If errors, return them before creating an instance in the database\r
+ if (\r
+ this.errors.processDefinitionKey ||\r
+ this.errors.notFound ||\r
+ this.errors.testHead ||\r
+ this.errors.permissions ||\r
+ this.errors.required ||\r
+ this.errors.startEvent\r
+ ) {\r
+ return new Response(400, {}, { bpmnVthTaskIds: this.bpmnVthTaskIds, errors: this.errors });\r
+ }\r
+\r
+ //set the new process Definition key\r
+ //console.log('END Process Key: ' + this.processDefinitionKey);\r
+ this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0].$.id = this.processDefinitionKey;\r
+\r
+ //build xml from the json object\r
+ var xmlBuilder = new Builder();\r
+ var xmlToSend = xmlBuilder.buildObject(this.parsedXMLtoJSON);\r
+ \r
+ //console.log(this.bpmnVthTaskIds);\r
+ \r
+ let response = { \r
+ bpmnXml: xmlToSend, \r
+ bpmnVthTaskIds: this.bpmnVthTaskIds, \r
+ bpmnPfloTaskIds: this.bpmnPfloTaskIds, \r
+ processDefinitionKey: this.processDefinitionKey, \r
+ };\r
+\r
+ //if there are errors\r
+ if(JSON.stringify(this.errors) != "{}"){\r
+ response.errors = this.errors\r
+ }\r
+\r
+ return new Response(200, {}, response);\r
+ \r
+ }\r
+\r
+ async findTasks (process) {\r
+ //If there are service tasks in the diagram\r
+ if(process['bpmn:serviceTask']){\r
+ //console.log('has service task');\r
+ // Go through all of the service task boxes\r
+ for (let j = 0; j < process['bpmn:serviceTask'].length; j++) {\r
+ //console.log(process['bpmn:serviceTask'][j])\r
+\r
+ //check that the service task is not on the DO NOT ALLOW list\r
+ for(let n = 0; n < this.serviceTasksNotAllowed.length; n++){\r
+ //check cammunda keys\r
+ if(process['bpmn:serviceTask'][j].$[this.serviceTasksNotAllowed[n].key]){\r
+ if(!this.errors.permissions){\r
+ this.errors.permissions = [];\r
+ }\r
+ this.errors.permissions.push({error: this.serviceTasksNotAllowed[n].key + ' is not allowed.'})\r
+ }\r
+ }\r
+\r
+ //Clear any user defined delegate expressions\r
+ if(process['bpmn:serviceTask'][j].$['camunda:delegateExpression']){\r
+ process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = '';\r
+ }\r
+ \r
+ //Go through all the delegates that are defined by OTF (in constructor)\r
+ for (let d = 0; d < this.delegates.length; d++){\r
+ var match = null;\r
+ \r
+ if(match = process['bpmn:serviceTask'][j].$.name.match(this.delegates[d].regex)){\r
+ //console.log(match)\r
+ switch(this.delegates[d].name){\r
+ case 'vth':\r
+ case 'cvth':\r
+ case 'lvth':\r
+ await this.checkTestHead(match, process['bpmn:serviceTask'][j]);\r
+ break;\r
+\r
+ case 'pflo':\r
+ let temp = {bpmnPfloTaskId: process['bpmn:serviceTask'][j].$.id};\r
+ if(match[2]){\r
+ temp['label'] = match[2];\r
+ }\r
+ this.bpmnPfloTaskIds.push(temp);\r
+ break;\r
+ \r
+ case 'log':\r
+ this.hasLog = true;\r
+ break;\r
+ \r
+ }\r
+\r
+ if(this.delegates[d].topic){\r
+ process['bpmn:serviceTask'][j].$['camunda:type'] = 'external';\r
+ process['bpmn:serviceTask'][j].$['camunda:topic'] = this.delegates[d].topic;\r
+ }else{\r
+ process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
+ }\r
+\r
+ break;\r
+\r
+ }\r
+ }\r
+\r
+ }\r
+ } //end if service task\r
+\r
+ if(process['bpmn:task']){\r
+ //console.log('has task')\r
+ //init service task array \r
+ if(!process['bpmn:serviceTask']){\r
+ process['bpmn:serviceTask'] = [];\r
+ }\r
+\r
+ // Go through all of the task boxes\r
+ for (let j = 0; j < process['bpmn:task'].length; j++) {\r
+ //console.log(process['bpmn:task'][j])\r
+\r
+ for (let d = 0; d < this.delegates.length; d++){\r
+ var match = null;\r
+ \r
+ if(match = process['bpmn:task'][j].$.name.match(this.delegates[d].regex)){\r
+ //console.log(match)\r
+ switch(this.delegates[d].name){\r
+ case 'vth':\r
+ case 'cvth':\r
+ case 'lvth':\r
+ await this.checkTestHead(match, process['bpmn:task'][j]);\r
+ break;\r
+ \r
+ case 'pflo':\r
+ let temp = {bpmnPfloTaskId: process['bpmn:task'][j].$.id};\r
+ if(match[2]){\r
+ temp['label'] = match[2];\r
+ }\r
+ this.bpmnPfloTaskIds.push(temp);\r
+ break;\r
+ \r
+ case 'log':\r
+ this.hasLog = true;\r
+ break;\r
+ \r
+ }\r
+\r
+ let task = {\r
+ $: {\r
+ id: process['bpmn:task'][j].$.id,\r
+ name: process['bpmn:task'][j].$.name,\r
+ },\r
+ 'bpmn:incoming': process['bpmn:task'][j]['bpmn:incoming'],\r
+ 'bpmn:outgoing': process['bpmn:task'][j]['bpmn:outgoing']\r
+ }\r
+\r
+ if(this.delegates[d].topic){\r
+ task.$['camunda:type'] = 'external';\r
+ task.$['camunda:topic'] = this.delegates[d].topic;\r
+ }else{\r
+ task.$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
+ }\r
+\r
+ process['bpmn:serviceTask'].push(task);\r
+\r
+ process['bpmn:task'].splice(j, 1);\r
+ j--;\r
+\r
+ break;\r
+\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ }\r
+\r
+ //If subprocess, find tasks\r
+ if(process['bpmn:subProcess']){\r
+ for(let p = 0; p < process['bpmn:subProcess'].length; p++){\r
+ await this.findTasks(process['bpmn:subProcess'][p]);\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ async checkTestHead(match, task){\r
+ if (match.length >= 4) {\r
+ match[3] = '^' + match[3] + '$';\r
+ this.params.query = { testHeadName: new RegExp(match[3], 'i')};\r
+ delete this.params.paginate;\r
+ //console.log(this.params);\r
+ await this.app.services[this.app.get('base-path') + 'test-heads'].find(this.params)\r
+ .then(result => {\r
+ if (result.total > 1) {\r
+ // there should only be one test head found, else there is an error in the database\r
+ if (!this.errors.testHeads) {\r
+ this.errors.testHeads = [];\r
+ }\r
+ this.errors.testHeads.push({ error: result.total + ' test heads named: ' + match[3] });\r
+ }\r
+\r
+ if (result.total == 0) {\r
+ if(!this.errors.permissions){\r
+ this.errors.permissions = []\r
+ }\r
+ this.errors.permissions.push({ error: 'You do not have access to test head: ' + match[3] });\r
+ } else {\r
+ this.bpmnVthTaskIds.push({ testHead: result.data[0], bpmnVthTaskId: task.$.id });\r
+ }\r
+ })\r
+ .catch(err => {\r
+ //console.log(err);\r
+ this.errors.notFound = { error: 'Test head "' + match[3] + '" does not exist' };\r
+ });\r
+ } else if (match.length > 0) { // no test head name supplied\r
+\r
+ }\r
+ }\r
+\r
+}\r
+module.exports = function (app, data, params) {\r
+ return new Bpmn(app, data, params);\r
+};\r
+\r
+module.exports.Bpmn = Bpmn;
\ No newline at end of file