added svcapi ui and camunda code
[it/otf.git] / otf-frontend / server / src / feathers / services / bpmn-validate / bpmn.class.js
1 /*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
2 #                                                                              #\r
3 #   Licensed under the Apache License, Version 2.0 (the "License");            #\r
4 #   you may not use this file except in compliance with the License.           #\r
5 #   You may obtain a copy of the License at                                    #\r
6 #                                                                              #\r
7 #       http://www.apache.org/licenses/LICENSE-2.0                             #\r
8 #                                                                              #\r
9 #   Unless required by applicable law or agreed to in writing, software        #\r
10 #   distributed under the License is distributed on an "AS IS" BASIS,          #\r
11 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
12 #   See the License for the specific language governing permissions and        #\r
13 #   limitations under the License.                                             #\r
14 ##############################################################################*/\r
15 \r
16 \r
17 const { parseString, Builder } = require('xml2js');\r
18 const logger = require('../../../lib/logger');\r
19 const Response = require('http-response-object');\r
20 \r
21 class Bpmn {\r
22     constructor(app, data, params){\r
23                 this.delegates = [\r
24                         {\r
25                                 name: 'vth',\r
26                                 regex: new RegExp('^(vth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
27                                 delegate: '${callTestHeadDelegate}'\r
28                         },\r
29                         {\r
30                                 name: 'lvth',\r
31                                 regex: new RegExp('^(lvth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
32                                 topic: 'vth'\r
33                         },\r
34                         {\r
35                                 name: 'log',\r
36                                 regex: new RegExp('^UTIL:LogTestResult$', 'i'),\r
37                                 delegate: '${logTestResultDelegate}',\r
38                         },\r
39                         {\r
40                                 name: 'pflo',\r
41                                 regex: new RegExp('^PFLO(:(.+))?$', 'i'),\r
42                                 delegate: '${runTestInstanceDelegate}',\r
43                                 topic: 'testInstance'\r
44                         },\r
45                         {\r
46                                 name: 'SubFlow',\r
47                                 regex: new RegExp('^SUBFLOW(:(.+))?$', 'i'),\r
48                                 delegate: '${runTestInstanceDelegate}',\r
49                                 topic: 'testInstance'\r
50                         },\r
51                         {\r
52                                 name: 'dmaap',\r
53                                 regex: new RegExp('^PostResultsToDMaaP(:(.+))?$', 'i'),\r
54                                 delegate: '${postResultsToDMaaPDelegate}'\r
55                         },\r
56                         {\r
57                                 name: 'mail',\r
58                                 regex: new RegExp('^UTIL:SendMail(:(.+))?$', 'i'),\r
59                                 delegate: '${sendMailDelegate}'\r
60                         }\r
61                 ];\r
62 \r
63                 this.serviceTasksNotAllowed = [\r
64                         {\r
65                                 key: 'camunda:class'\r
66                         }\r
67                 ]\r
68         \r
69         this.params = params;\r
70         this.data = data;\r
71         this.app = app;\r
72                 this.parsedXMLtoJSON = null;\r
73                 this.bpmnVthTaskIds = [];\r
74                 this.bpmnPfloTaskIds = [];\r
75                 this.processDefinitionKey = null;\r
76                 this.errors = {};\r
77                 this.hasLog = false; //1 log is required in each workflow\r
78                 this.hasTestHeads = false;\r
79                 \r
80     }\r
81 \r
82     async validate(){\r
83                 //convert bpmn to json\r
84                 //console.log(this.data.testDefinition);\r
85                 parseString(\r
86                         this.data.testDefinition.bpmnInstances[this.data.testDefinition.currentVersion].bpmnXml,\r
87                         (err, result) => {\r
88                                 if (err) {\r
89                                         logger.error(err);\r
90                                 }\r
91                                 this.parsedXMLtoJSON = Object.assign({}, result);\r
92             }\r
93                 );\r
94 \r
95         //If the bpmn was unable to be parsed, return error response\r
96         if (!this.parsedXMLtoJSON) {\r
97                         return new Response(500, {}, { errors: { parsingError: { error: 'Failed to parse bpmn. Try Again.' } } });\r
98         }\r
99 \r
100         //set temp process\r
101                 var process = this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0];\r
102 \r
103 \r
104                 // Not needed with new modeler\r
105         //If a process definition key was sent with the requrest, use it instead\r
106                 if (this.data.testDefinition.processDefinitionKey && this.data.testDefinition.processDefinitionKey != '') {\r
107                         this.processDefinitionKey = this.data.testDefinition.processDefinitionKey;\r
108                 }else{\r
109                     this.processDefinitionKey = process.$.id;\r
110         }\r
111         \r
112         //Check to see if the process definition key is unique\r
113         let key = await this.app.services[this.app.get('base-path') + 'bpmn-validate'].get(this.processDefinitionKey, this.params).then();\r
114         if(key.statusCode != 200 && key.errors && key.errors.processDefinitionKey){\r
115             this.errors.processDefinitionKey = {\r
116                 error: 'Process Definition Key has already been used',\r
117                 key: this.processDefinitionKey\r
118             };\r
119         }\r
120         \r
121         // Verify start task(s) are async. Only start task(s) in main process\r
122                 if (process['bpmn:startEvent']) {\r
123                         for (var j = 0; j < process['bpmn:startEvent'].length; j++) {\r
124                                 var startEvent = process['bpmn:startEvent'][j];\r
125                                 if (startEvent.$['camunda:asyncBefore'] != 'true') {\r
126                                         this.errors.startEvent = { error: 'Start Event, ' + startEvent.$.id + ', is not async' };\r
127                                 }\r
128                         }\r
129                 } else {\r
130                         this.errors.startEvent = { error: 'Workflow must have a start even' };\r
131         }\r
132         \r
133         //Find all of the task boxes that need to be changed (recursive)\r
134         await this.findTasks(this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0]);\r
135         \r
136         // If log task did not exist, log\r
137                 if (!this.hasLog) {\r
138                         this.errors.required = { error: 'No LogSetResult task. One is required.' };\r
139         }\r
140         \r
141         // If errors, return them before creating an instance in the database\r
142                 if (\r
143                         this.errors.processDefinitionKey ||\r
144                         this.errors.notFound ||\r
145                         this.errors.testHead ||\r
146                         this.errors.permissions ||\r
147                         this.errors.required ||\r
148                         this.errors.startEvent\r
149                 ) {\r
150                         return new Response(400, {}, { bpmnVthTaskIds: this.bpmnVthTaskIds, errors: this.errors });\r
151                 }\r
152 \r
153         //set the new process Definition key\r
154         //console.log('END Process Key: ' + this.processDefinitionKey);\r
155                 this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0].$.id = this.processDefinitionKey;\r
156 \r
157         //build xml from the json object\r
158                 var xmlBuilder = new Builder();\r
159         var xmlToSend = xmlBuilder.buildObject(this.parsedXMLtoJSON);\r
160         \r
161                 //console.log(this.bpmnVthTaskIds);\r
162                 \r
163                 let response = { \r
164                         bpmnXml: xmlToSend, \r
165                         bpmnVthTaskIds: this.bpmnVthTaskIds, \r
166                         bpmnPfloTaskIds: this.bpmnPfloTaskIds, \r
167                         processDefinitionKey: this.processDefinitionKey, \r
168                 };\r
169 \r
170                 //if there are errors\r
171                 if(JSON.stringify(this.errors) != "{}"){\r
172                         response.errors = this.errors\r
173                 }\r
174 \r
175                 return new Response(200, {}, response);\r
176         \r
177     }\r
178 \r
179     async findTasks (process) {\r
180                 //If there are service tasks in the diagram\r
181                 if(process['bpmn:serviceTask']){\r
182                         //console.log('has service task');\r
183                         // Go through all of the service task boxes\r
184                         for (let j = 0; j < process['bpmn:serviceTask'].length; j++) {\r
185                                 //console.log(process['bpmn:serviceTask'][j])\r
186 \r
187                                 //check that the service task is not on the DO NOT ALLOW list\r
188                                 for(let n = 0; n < this.serviceTasksNotAllowed.length; n++){\r
189                                         //check cammunda keys\r
190                                         if(process['bpmn:serviceTask'][j].$[this.serviceTasksNotAllowed[n].key]){\r
191                                                 if(!this.errors.permissions){\r
192                                                         this.errors.permissions = [];\r
193                                                 }\r
194                                                 this.errors.permissions.push({error: this.serviceTasksNotAllowed[n].key + ' is not allowed.'})\r
195                                         }\r
196                                 }\r
197 \r
198                                 //Clear any user defined delegate expressions\r
199                                 if(process['bpmn:serviceTask'][j].$['camunda:delegateExpression']){\r
200                                         process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = '';\r
201                                 }\r
202                                 \r
203                                 //Go through all the delegates that are defined by OTF (in constructor)\r
204                                 for (let d = 0; d < this.delegates.length; d++){\r
205                                         var match = null;\r
206                                         \r
207                                         if(match = process['bpmn:serviceTask'][j].$.name.match(this.delegates[d].regex)){\r
208                                                 //console.log(match)\r
209                                                 switch(this.delegates[d].name){\r
210                                                         case 'vth':\r
211                                                         case 'cvth':\r
212                                                         case 'lvth':\r
213                                                                 await this.checkTestHead(match, process['bpmn:serviceTask'][j]);\r
214                                                                 break;\r
215 \r
216                                                         case 'pflo':\r
217                                                                 let temp = {bpmnPfloTaskId: process['bpmn:serviceTask'][j].$.id};\r
218                                                                 if(match[2]){\r
219                                                                         temp['label'] = match[2];\r
220                                                                 }\r
221                                                                 this.bpmnPfloTaskIds.push(temp);\r
222                                                                 break;\r
223                                                         \r
224                                                         case 'log':\r
225                                                                 this.hasLog = true;\r
226                                                                 break;\r
227                                                         \r
228                                                 }\r
229 \r
230                                                 if(this.delegates[d].topic){\r
231                                                         process['bpmn:serviceTask'][j].$['camunda:type'] = 'external';\r
232                                                         process['bpmn:serviceTask'][j].$['camunda:topic'] = this.delegates[d].topic;\r
233                                                 }else{\r
234                                                         process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
235                                                 }\r
236 \r
237                                                 break;\r
238 \r
239                                         }\r
240                                 }\r
241 \r
242                         }\r
243                 } //end if service task\r
244 \r
245                 if(process['bpmn:task']){\r
246                         //console.log('has task')\r
247                         //init service task array \r
248                         if(!process['bpmn:serviceTask']){\r
249                                 process['bpmn:serviceTask'] = [];\r
250                         }\r
251 \r
252                         // Go through all of the task boxes\r
253                         for (let j = 0; j < process['bpmn:task'].length; j++) {\r
254                                 //console.log(process['bpmn:task'][j])\r
255 \r
256                                 for (let d = 0; d < this.delegates.length; d++){\r
257                                         var match = null;\r
258                                         \r
259                                         if(match = process['bpmn:task'][j].$.name.match(this.delegates[d].regex)){\r
260                                                 //console.log(match)\r
261                                                 switch(this.delegates[d].name){\r
262                                                         case 'vth':\r
263                                                         case 'cvth':\r
264                                                         case 'lvth':\r
265                                                                 await this.checkTestHead(match, process['bpmn:task'][j]);\r
266                                                                 break;\r
267                                                         \r
268                                                         case 'pflo':\r
269                                                                 let temp = {bpmnPfloTaskId: process['bpmn:task'][j].$.id};\r
270                                                                 if(match[2]){\r
271                                                                         temp['label'] = match[2];\r
272                                                                 }\r
273                                                                 this.bpmnPfloTaskIds.push(temp);\r
274                                                                 break;\r
275                                                         \r
276                                                         case 'log':\r
277                                                                 this.hasLog = true;\r
278                                                                 break;\r
279                                                         \r
280                                                 }\r
281 \r
282                                                 let task = {\r
283                                                         $: {\r
284                                                                 id: process['bpmn:task'][j].$.id,\r
285                                                                 name: process['bpmn:task'][j].$.name,\r
286                                                         },\r
287                                                         'bpmn:incoming': process['bpmn:task'][j]['bpmn:incoming'],\r
288                                                         'bpmn:outgoing': process['bpmn:task'][j]['bpmn:outgoing']\r
289                                                 }\r
290 \r
291                                                 if(this.delegates[d].topic){\r
292                                                         task.$['camunda:type'] = 'external';\r
293                                                         task.$['camunda:topic'] = this.delegates[d].topic;\r
294                                                 }else{\r
295                                                         task.$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
296                                                 }\r
297 \r
298                                                 process['bpmn:serviceTask'].push(task);\r
299 \r
300                                                 process['bpmn:task'].splice(j, 1);\r
301                                                 j--;\r
302 \r
303                                                 break;\r
304 \r
305                                         }\r
306                                 }\r
307 \r
308                         }\r
309 \r
310                 }\r
311 \r
312                 //If subprocess, find tasks\r
313                 if(process['bpmn:subProcess']){\r
314                         for(let p = 0; p < process['bpmn:subProcess'].length; p++){\r
315                                 await this.findTasks(process['bpmn:subProcess'][p]);\r
316                         }\r
317                 }\r
318                 \r
319         }\r
320 \r
321         async checkTestHead(match, task){\r
322                 if (match.length >= 4) {\r
323                         match[3] = '^' + match[3] + '$';\r
324                         this.params.query = { testHeadName: new RegExp(match[3], 'i')};\r
325                         delete this.params.paginate;\r
326                         //console.log(this.params);\r
327                         await this.app.services[this.app.get('base-path') + 'test-heads'].find(this.params)\r
328                                 .then(result => {\r
329                                         if (result.total > 1) {\r
330                                                 // there should only be one test head found, else there is an error in the database\r
331                                                 if (!this.errors.testHeads) {\r
332                                                         this.errors.testHeads = [];\r
333                                                 }\r
334                                                 this.errors.testHeads.push({ error: result.total + ' test heads named: ' + match[3] });\r
335                                         }\r
336 \r
337                                         if (result.total == 0) {\r
338                                                 if(!this.errors.permissions){\r
339                                                         this.errors.permissions = []\r
340                                                 }\r
341                                                 this.errors.permissions.push({ error: 'You do not have access to test head: ' + match[3] });\r
342                                         } else {\r
343                                                 this.bpmnVthTaskIds.push({ testHead: result.data[0], bpmnVthTaskId: task.$.id });\r
344                                         }\r
345                                 })\r
346                                 .catch(err => {\r
347                                         //console.log(err);\r
348                                         this.errors.notFound = { error: 'Test head "' + match[3] + '" does not exist' };\r
349                                 });\r
350                 } else if (match.length > 0) { // no test head name supplied\r
351 \r
352                 }\r
353         }\r
354 \r
355 }\r
356 module.exports = function (app, data, params) {\r
357         return new Bpmn(app, data, params);\r
358 };\r
359 \r
360 module.exports.Bpmn = Bpmn;