added svcapi ui and camunda code
[it/otf.git] / otf-frontend / server / src / feathers / services / bpmn-validate / bpmn.class.js
diff --git a/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js
new file mode 100644 (file)
index 0000000..620f9bd
--- /dev/null
@@ -0,0 +1,360 @@
+/*  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