1 /* Copyright (c) 2019 AT&T Intellectual Property. #
\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
7 # http://www.apache.org/licenses/LICENSE-2.0 #
\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
17 const { parseString, Builder } = require('xml2js');
\r
18 const logger = require('../../../lib/logger');
\r
19 const Response = require('http-response-object');
\r
22 constructor(app, data, params){
\r
26 regex: new RegExp('^(vth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),
\r
27 delegate: '${callTestHeadDelegate}'
\r
31 regex: new RegExp('^(lvth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),
\r
36 regex: new RegExp('^UTIL:LogTestResult$', 'i'),
\r
37 delegate: '${logTestResultDelegate}',
\r
41 regex: new RegExp('^PFLO(:(.+))?$', 'i'),
\r
42 delegate: '${runTestInstanceDelegate}',
\r
43 topic: 'testInstance'
\r
47 regex: new RegExp('^SUBFLOW(:(.+))?$', 'i'),
\r
48 delegate: '${runTestInstanceDelegate}',
\r
49 topic: 'testInstance'
\r
53 regex: new RegExp('^PostResultsToDMaaP(:(.+))?$', 'i'),
\r
54 delegate: '${postResultsToDMaaPDelegate}'
\r
58 regex: new RegExp('^UTIL:SendMail(:(.+))?$', 'i'),
\r
59 delegate: '${sendMailDelegate}'
\r
63 this.serviceTasksNotAllowed = [
\r
65 key: 'camunda:class'
\r
69 this.params = params;
\r
72 this.parsedXMLtoJSON = null;
\r
73 this.bpmnVthTaskIds = [];
\r
74 this.bpmnPfloTaskIds = [];
\r
75 this.processDefinitionKey = null;
\r
77 this.hasLog = false; //1 log is required in each workflow
\r
78 this.hasTestHeads = false;
\r
83 //convert bpmn to json
\r
84 //console.log(this.data.testDefinition);
\r
86 this.data.testDefinition.bpmnInstances[this.data.testDefinition.currentVersion].bpmnXml,
\r
91 this.parsedXMLtoJSON = Object.assign({}, result);
\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
101 var process = this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0];
\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
109 this.processDefinitionKey = process.$.id;
\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
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
130 this.errors.startEvent = { error: 'Workflow must have a start even' };
\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
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
141 // If errors, return them before creating an instance in the database
\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
150 return new Response(400, {}, { bpmnVthTaskIds: this.bpmnVthTaskIds, errors: this.errors });
\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
157 //build xml from the json object
\r
158 var xmlBuilder = new Builder();
\r
159 var xmlToSend = xmlBuilder.buildObject(this.parsedXMLtoJSON);
\r
161 //console.log(this.bpmnVthTaskIds);
\r
164 bpmnXml: xmlToSend,
\r
165 bpmnVthTaskIds: this.bpmnVthTaskIds,
\r
166 bpmnPfloTaskIds: this.bpmnPfloTaskIds,
\r
167 processDefinitionKey: this.processDefinitionKey,
\r
170 //if there are errors
\r
171 if(JSON.stringify(this.errors) != "{}"){
\r
172 response.errors = this.errors
\r
175 return new Response(200, {}, response);
\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
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
194 this.errors.permissions.push({error: this.serviceTasksNotAllowed[n].key + ' is not allowed.'})
\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
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
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
213 await this.checkTestHead(match, process['bpmn:serviceTask'][j]);
\r
217 let temp = {bpmnPfloTaskId: process['bpmn:serviceTask'][j].$.id};
\r
219 temp['label'] = match[2];
\r
221 this.bpmnPfloTaskIds.push(temp);
\r
225 this.hasLog = true;
\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
234 process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = this.delegates[d].delegate;
\r
243 } //end if service task
\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
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
256 for (let d = 0; d < this.delegates.length; d++){
\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
265 await this.checkTestHead(match, process['bpmn:task'][j]);
\r
269 let temp = {bpmnPfloTaskId: process['bpmn:task'][j].$.id};
\r
271 temp['label'] = match[2];
\r
273 this.bpmnPfloTaskIds.push(temp);
\r
277 this.hasLog = true;
\r
284 id: process['bpmn:task'][j].$.id,
\r
285 name: process['bpmn:task'][j].$.name,
\r
287 'bpmn:incoming': process['bpmn:task'][j]['bpmn:incoming'],
\r
288 'bpmn:outgoing': process['bpmn:task'][j]['bpmn:outgoing']
\r
291 if(this.delegates[d].topic){
\r
292 task.$['camunda:type'] = 'external';
\r
293 task.$['camunda:topic'] = this.delegates[d].topic;
\r
295 task.$['camunda:delegateExpression'] = this.delegates[d].delegate;
\r
298 process['bpmn:serviceTask'].push(task);
\r
300 process['bpmn:task'].splice(j, 1);
\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
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
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
334 this.errors.testHeads.push({ error: result.total + ' test heads named: ' + match[3] });
\r
337 if (result.total == 0) {
\r
338 if(!this.errors.permissions){
\r
339 this.errors.permissions = []
\r
341 this.errors.permissions.push({ error: 'You do not have access to test head: ' + match[3] });
\r
343 this.bpmnVthTaskIds.push({ testHead: result.data[0], bpmnVthTaskId: task.$.id });
\r
347 //console.log(err);
\r
348 this.errors.notFound = { error: 'Test head "' + match[3] + '" does not exist' };
\r
350 } else if (match.length > 0) { // no test head name supplied
\r
356 module.exports = function (app, data, params) {
\r
357 return new Bpmn(app, data, params);
\r
360 module.exports.Bpmn = Bpmn;