Springboot 2.0 upgrade
[so.git] / bpmn / mso-infrastructure-bpmn / src / test / java / org / onap / so / bpmn / common / WorkflowTest.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.so.bpmn.common;
22
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.fail;
25 import static org.onap.so.bpmn.core.json.JsonUtils.getJsonValue;
26 import static org.onap.so.bpmn.core.json.JsonUtils.updJsonValue;
27
28 import java.io.IOException;
29 import java.io.StringReader;
30 import java.lang.management.ManagementFactory;
31 import java.lang.management.RuntimeMXBean;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.UUID;
38
39 import javax.ws.rs.core.Response;
40 import javax.xml.bind.JAXBException;
41 import javax.xml.namespace.NamespaceContext;
42 import javax.xml.namespace.QName;
43 import javax.xml.parsers.DocumentBuilder;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.parsers.ParserConfigurationException;
46 import javax.xml.xpath.XPath;
47 import javax.xml.xpath.XPathConstants;
48 import javax.xml.xpath.XPathExpression;
49 import javax.xml.xpath.XPathExpressionException;
50 import javax.xml.xpath.XPathFactory;
51
52 import org.camunda.bpm.engine.HistoryService;
53 import org.camunda.bpm.engine.ProcessEngine;
54 import org.camunda.bpm.engine.ProcessEngineException;
55 import org.camunda.bpm.engine.RuntimeService;
56 import org.camunda.bpm.engine.history.HistoricProcessInstance;
57 import org.camunda.bpm.engine.history.HistoricVariableInstance;
58 import org.camunda.bpm.engine.runtime.ProcessInstance;
59 import org.camunda.bpm.engine.test.ProcessEngineRule;
60 import org.camunda.bpm.engine.variable.impl.VariableMapImpl;
61 import org.custommonkey.xmlunit.DetailedDiff;
62 import org.custommonkey.xmlunit.XMLUnit;
63 import org.json.JSONArray;
64 import org.json.JSONObject;
65 import org.junit.Rule;
66 import org.onap.so.bpmn.common.adapter.sdnc.CallbackHeader;
67 import org.onap.so.bpmn.common.adapter.sdnc.SDNCAdapterCallbackRequest;
68 import org.onap.so.bpmn.common.adapter.sdnc.SDNCAdapterResponse;
69 import org.onap.so.bpmn.common.adapter.vnf.CreateVnfNotification;
70 import org.onap.so.bpmn.common.adapter.vnf.DeleteVnfNotification;
71 import org.onap.so.bpmn.common.adapter.vnf.MsoExceptionCategory;
72 import org.onap.so.bpmn.common.adapter.vnf.MsoRequest;
73 import org.onap.so.bpmn.common.adapter.vnf.UpdateVnfNotification;
74 import org.onap.so.bpmn.common.adapter.vnf.VnfRollback;
75 import org.onap.so.bpmn.common.workflow.context.WorkflowResponse;
76 import org.onap.so.bpmn.common.workflow.service.SDNCAdapterCallbackServiceImpl;
77 import org.onap.so.bpmn.common.workflow.service.VnfAdapterNotifyServiceImpl;
78 import org.onap.so.bpmn.common.workflow.service.WorkflowAsyncResource;
79 import org.onap.so.bpmn.common.workflow.service.WorkflowMessageResource;
80 import org.onap.so.bpmn.common.workflow.service.WorkflowResource;
81 import org.onap.so.bpmn.core.domain.Resource;
82 import org.onap.so.bpmn.core.domain.ServiceDecomposition;
83 import org.onap.so.logger.MsoLogger;
84 import org.springframework.beans.factory.annotation.Autowired;
85 import org.w3c.dom.Document;
86 import org.w3c.dom.Element;
87 import org.w3c.dom.Node;
88 import org.w3c.dom.NodeList;
89 import org.xml.sax.InputSource;
90 import org.xml.sax.SAXException;
91
92
93
94 /**
95  * A base class for Workflow tests.
96  * <p>
97  * WireMock response transformers may be specified by declaring public
98  * static fields with the @WorkflowTestTransformer annotation. For example:
99  * <pre>
100  *     @WorkflowTestTransformer
101  *     public static final ResponseTransformer sdncAdapterMockTransformer =
102  *         new SDNCAdapterMockTransformer();
103  * </pre>
104  */
105
106 public abstract class WorkflowTest {
107
108         private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, WorkflowTest.class);
109
110         //TODO this is not used anymore, can maybe be removed
111         @Rule
112         public ProcessEngineRule processEngineRule;
113
114         @Autowired
115         protected WorkflowResource workflowResourceSync;
116
117         @Autowired
118         protected ProcessEngine processEngine;
119
120         @Autowired
121         protected RuntimeService runtimeService;
122
123         @Autowired
124         protected HistoryService historyService;
125
126         @Autowired
127         private WorkflowAsyncResource workflowResource;
128
129         @Autowired
130         private WorkflowMessageResource workflowMessageResource;
131
132         @Autowired
133         SDNCAdapterCallbackServiceImpl callbackService;
134         /**
135          * Content-Type for XML.
136          */
137         protected static final String XML = "application/xml";
138
139         /**
140          * Content-Type for JSON.
141          */
142         protected static final String JSON = "application/json; charset=UTF-8";
143
144         private static final int timeout = 2000;
145         
146         /**
147          * Constructor.
148          */
149         public WorkflowTest() throws RuntimeException {
150         }
151
152         /**
153          * The current request ID.  Normally set when an "invoke" method is called.
154          */
155         protected volatile String msoRequestId = null;
156
157         /**
158          * The current service instance ID.  Normally set when an "invoke" method
159          * is called.
160          */
161         protected volatile String msoServiceInstanceId = null;
162
163         /**
164          * Logs a test start method.
165          */
166         protected void logStart() {
167                 msoLogger.debug("STARTED TEST");
168         }
169
170         /**
171          * Logs a test end method.
172          */
173         protected void logEnd() {
174                 msoLogger.debug("ENDED TEST");
175         }
176
177         /**
178          * Invokes a subprocess.
179          * @param processKey the process key
180          * @param businessKey a unique key that will identify the process instance
181          * @param injectedVariables variables to inject into the process
182          */
183         protected void invokeSubProcess(String processKey, String businessKey, Map<String, Object> injectedVariables) {
184                 RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
185                 List<String> arguments = runtimeMxBean.getInputArguments();
186                 msoLogger.debug("JVM args = " + arguments);
187
188                 msoRequestId = (String) injectedVariables.get("mso-request-id");
189                 String requestId = (String) injectedVariables.get("msoRequestId");
190
191                 if (msoRequestId == null && requestId == null) {
192                         String msg = "mso-request-id variable was not provided";
193                         msoLogger.debug(msg);
194                         fail(msg);
195                 }
196
197                 // Note: some scenarios don't have a service-instance-id, may be null
198                 msoServiceInstanceId = (String) injectedVariables.get("mso-service-instance-id");
199
200
201                 runtimeService.startProcessInstanceByKey(processKey, businessKey, injectedVariables);
202         }
203
204         protected String invokeSubProcess(String processKey,  Map<String, Object> injectedVariables) {
205                 RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
206                 List<String> arguments = runtimeMxBean.getInputArguments();
207                 msoLogger.debug("JVM args = " + arguments);
208
209                 msoRequestId = (String) injectedVariables.get("mso-request-id");
210                 String requestId = (String) injectedVariables.get("msoRequestId");
211
212                 if (msoRequestId == null && requestId == null) {
213                         String msg = "mso-request-id variable was not provided";
214                         msoLogger.debug(msg);
215                         fail(msg);
216                 }
217
218                 // Note: some scenarios don't have a service-instance-id, may be null
219                 msoServiceInstanceId = (String) injectedVariables.get("mso-service-instance-id");
220
221
222                 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processKey, msoRequestId, injectedVariables);
223                 return processInstance.getId();
224         }
225
226         /**
227          * Invokes an asynchronous process.
228          * Errors are handled with junit assertions and will cause the test to fail.
229          * @param processKey the process key
230          * @param schemaVersion the API schema version, e.g. "v1"
231          * @param businessKey a unique key that will identify the process instance
232          * @param request the request
233          * @return a TestAsyncResponse object associated with the test
234          * @throws InterruptedException
235          */
236         protected TestAsyncResponse invokeAsyncProcess(String processKey,
237                         String schemaVersion, String businessKey, String request) throws InterruptedException {
238                 return invokeAsyncProcess(processKey, schemaVersion, businessKey, request, null);
239         }
240
241         /**
242          * Invokes an asynchronous process.
243          * Errors are handled with junit assertions and will cause the test to fail.
244          * @param processKey the process key
245          * @param schemaVersion the API schema version, e.g. "v1"
246          * @param businessKey a unique key that will identify the process instance
247          * @param request the request
248          * @param injectedVariables optional variables to inject into the process
249          * @return a TestAsyncResponse object associated with the test
250          * @throws InterruptedException
251          */
252         protected TestAsyncResponse invokeAsyncProcess(String processKey,
253                         String schemaVersion, String businessKey, String request,
254                         Map<String, Object> injectedVariables) {
255
256                 RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
257                 List<String> arguments = runtimeMxBean.getInputArguments();
258                 msoLogger.debug("JVM args = " + arguments);
259
260                 Map<String, Object> variables = createVariables(schemaVersion, businessKey,
261                         request, injectedVariables, false);
262                 VariableMapImpl variableMapImpl = createVariableMapImpl(variables);
263
264                 msoLogger.debug("Sending " + request + " to " + processKey + " process");
265
266                 TestAsyncResponse asyncResponse = new TestAsyncResponse();
267
268                 asyncResponse.setResponse(workflowResource.startProcessInstanceByKey( processKey, variableMapImpl));
269
270                 return asyncResponse;
271         }
272
273         /**
274          * Invokes an asynchronous process.
275          * Errors are handled with junit assertions and will cause the test to fail.
276          * @param processKey the process key
277          * @param schemaVersion the API schema version, e.g. "v1"
278          * @param businessKey a unique key that will identify the process instance
279          * @param request the request
280          * @param injectedVariables optional variables to inject into the process
281          * @param serviceInstantiationModel indicates whether this method is being
282          * invoked for a flow that is designed using the service instantiation model
283          * @return a TestAsyncResponse object associated with the test
284          * @throws InterruptedException
285          */
286         protected Response invokeAsyncProcess(String processKey,
287                         String schemaVersion, String businessKey, String request,
288                         Map<String, Object> injectedVariables, boolean serviceInstantiationModel) {
289
290                 RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
291                 List<String> arguments = runtimeMxBean.getInputArguments();
292                 msoLogger.debug("JVM args = " + arguments);
293
294                 Map<String, Object> variables = createVariables(schemaVersion, businessKey,
295                         request, injectedVariables, serviceInstantiationModel);
296                 VariableMapImpl variableMapImpl = createVariableMapImpl(variables);
297
298                 msoLogger.debug("Sending " + request + " to " + processKey + " process");
299
300                 return workflowResource.startProcessInstanceByKey( processKey, variableMapImpl);
301
302         }
303
304         /**
305          * Private helper method that creates a variable map for a request.
306          * Errors are handled with junit assertions and will cause the test to fail.
307          * @param schemaVersion the API schema version, e.g. "v1"
308          * @param businessKey a unique key that will identify the process instance
309          * @param request the request
310          * @param injectedVariables optional variables to inject into the process
311          * @param serviceInstantiationModel indicates whether this method is being
312          * invoked for a flow that is designed using the service instantiation model
313          * @return a variable map
314          */
315         private Map<String, Object> createVariables(String schemaVersion,
316                         String businessKey, String request, Map<String, Object> injectedVariables,
317                         boolean serviceInstantiationModel) {
318
319                 Map<String, Object> variables = new HashMap<>();
320
321                 // These variables may be overridded by injected variables.
322                 variables.put("mso-service-request-timeout", "180");
323                 variables.put("isDebugLogEnabled", "true");
324
325                 // These variables may not be overridded by injected variables.
326                 String[] notAllowed = new String[] {
327                                 "mso-schema-version",
328                                 "mso-business-key",
329                                 "bpmnRequest",
330                                 "mso-request-id",
331                                 "mso-service-instance-id"
332                 };
333
334                 if (injectedVariables != null) {
335                         for (String key : injectedVariables.keySet()) {
336                                 for (String var : notAllowed) {
337                                         if (var.equals(key)) {
338                                                 String msg = "Cannot specify " + var + " in injected variables";
339                                                 msoLogger.debug(msg);
340                                                 fail(msg);
341                                         }
342                                 }
343
344                                 variables.put(key, injectedVariables.get(key));
345                         }
346                 }
347
348                 variables.put("mso-schema-version", schemaVersion);
349                 variables.put("mso-business-key", businessKey);
350                 variables.put("bpmnRequest", request);
351
352                 if (serviceInstantiationModel) {
353
354                         /*
355                          * The request ID and the service instance ID are generated for flows
356                          * that follow the service instantiation model unless "requestId" and
357                          * "serviceInstanceId" are injected variables.
358                          */
359
360                         try {
361                                 msoRequestId = (String) injectedVariables.get("requestId");
362                                 variables.put("mso-request-id", msoRequestId);
363                                 msoServiceInstanceId = (String) injectedVariables.get("serviceInstanceId");
364                                 variables.put("mso-service-instance-id", msoServiceInstanceId);
365                         }
366                         catch(Exception e) {
367                         }
368                         if (msoRequestId == null || msoRequestId.trim().equals("")) {
369                                 msoLogger.debug("No requestId element in injectedVariables");
370                                 variables.put("mso-request-id", UUID.randomUUID().toString());
371                         }
372                         if (msoServiceInstanceId == null || msoServiceInstanceId.trim().equals("")) {
373                                 msoLogger.debug("No seviceInstanceId element in injectedVariables");
374                                 variables.put("mso-service-instance-id", UUID.randomUUID().toString());
375                         }
376
377                 } else {
378                         msoRequestId = getXMLTextElement(request, "request-id");
379
380                         if (msoRequestId == null) {
381                                 //check in injected variables
382                                 try {
383                                         msoRequestId = (String) injectedVariables.get("requestId");
384                                 }
385                                 catch(Exception e) {
386                                 }
387                                 if (msoRequestId == null || msoRequestId.trim().equals("")) {
388                                         String msg = "No request-id element in " + request;
389                                         msoLogger.debug(msg);
390                                         fail(msg);
391                                 }
392                         }
393
394                         variables.put("mso-request-id", msoRequestId);
395
396                         // Note: some request types don't have a service-instance-id
397                         msoServiceInstanceId = getXMLTextElement(request, "service-instance-id");
398
399                         if (msoServiceInstanceId != null) {
400                                 variables.put("mso-service-instance-id", msoServiceInstanceId);
401                         }
402                 }
403
404                 return variables;
405         }
406
407         /**
408          * Private helper method that creates a camunda VariableMapImpl from a simple
409          * variable map.
410          * @param variables the simple variable map
411          * @return a VariableMap
412          */
413         private VariableMapImpl createVariableMapImpl(Map<String, Object> variables) {
414                 Map<String, Object> wrappedVariables = new HashMap<>();
415
416                 for (String key : variables.keySet()) {
417                         Object value = variables.get(key);
418                         wrappedVariables.put(key, wrapVariableValue(value));
419                 }
420
421                 VariableMapImpl variableMapImpl = new VariableMapImpl();
422                 variableMapImpl.put("variables", wrappedVariables);
423                 return variableMapImpl;
424         }
425
426         /**
427          * Private helper method that wraps a variable value for inclusion in a
428          * camunda VariableMapImpl.
429          * @param value the variable value
430          * @return the wrapped variable
431          */
432         private Map<String, Object> wrapVariableValue(Object value) {
433                 HashMap<String, Object> valueMap = new HashMap<>();
434                 valueMap.put("value", value);
435                 return valueMap;
436         }
437
438         /**
439          * Receives a response from an asynchronous process.
440          * Errors are handled with junit assertions and will cause the test to fail.
441          * @param businessKey the process business key
442          * @param asyncResponse the TestAsyncResponse object associated with the test
443          * @param timeout the timeout in milliseconds
444          * @return the WorkflowResponse
445          */
446         protected WorkflowResponse receiveResponse(String businessKey,
447                         TestAsyncResponse asyncResponse, long timeout) {
448                 msoLogger.debug("Waiting " + timeout + "ms for process with business key " + businessKey
449                         + " to send a response");
450
451                 long now = System.currentTimeMillis() + timeout;
452                 long endTime = now + timeout;
453
454                 while (now <= endTime) {
455                         Response response = asyncResponse.getResponse();
456
457                         if (response != null) {
458                                 msoLogger.debug("Received a response from process with business key " + businessKey);
459
460                                 Object entity = response.getEntity();
461
462                                 if (!(entity instanceof WorkflowResponse)) {
463                                         String msg = "Response entity is " +
464                                                 (entity == null ? "null" : entity.getClass().getName()) +
465                                                 ", expected WorkflowResponse";
466                                         msoLogger.debug(msg);
467                                         fail(msg);
468                                         return null; // unreachable
469                                 }
470
471                                 return (WorkflowResponse) entity;
472                         }
473
474                         try {
475                                 Thread.sleep(200);
476                         } catch (InterruptedException e) {
477                                 String msg = "Interrupted waiting for a response from process with business key " +
478                                         businessKey;
479                                 msoLogger.debug(msg);
480                                 fail(msg);
481                                 return null; // unreachable
482                         }
483
484                         now = System.currentTimeMillis();
485                 }
486
487                 String msg = "No response received from process with business key " + businessKey +
488                         " within " + timeout + "ms";
489                 msoLogger.debug(msg);
490                 fail("Process with business key " + businessKey + " did not end within 10000ms");
491                 return null; // unreachable
492         }
493
494         /**
495          * Runs a program to inject SDNC callback data into the test environment.
496          * A program is essentially just a list of keys that identify callback data
497          * to be injected, in sequence. An example program:
498          * <pre>
499          *     reserve, assign, delete:ERR
500          * </pre>
501          * Errors are handled with junit assertions and will cause the test to fail.
502          * @param callbacks an object containing callback data for the program
503          * @param program the program to execute
504          */
505         protected void injectSDNCRestCallbacks(CallbackSet callbacks, String program) {
506
507                 String[] cmds = program.replaceAll("\\s+", "").split(",");
508
509                 for (String cmd : cmds) {
510                         String action = cmd;
511                         String modifier = "STD";
512
513                         if (cmd.contains(":")) {
514                                 String[] parts = cmd.split(":");
515                                 action = parts[0];
516                                 modifier = parts[1];
517                         }
518
519                         String content = null;
520                         String contentType = null;
521
522                         if ("STD".equals(modifier)) {
523                                 CallbackData callbackData = callbacks.get(action);
524
525                                 if (callbackData == null) {
526                                         String msg = "No callback defined for '" + action + "' SDNC request";
527                                         msoLogger.debug(msg);
528                                         fail(msg);
529                                 }
530
531                                 content = callbackData.getContent();
532                                 contentType = callbackData.getContentType();
533                         } else if ("ERR".equals(modifier)) {
534                                 content = "{\"SDNCServiceError\":{\"sdncRequestId\":\"((REQUEST-ID))\",\"responseCode\":\"500\",\"responseMessage\":\"SIMULATED ERROR FROM SDNC ADAPTER\",\"ackFinalIndicator\":\"Y\"}}";
535                                 contentType = JSON;
536                         } else {
537                                 String msg = "Invalid SDNC program modifier: '" + modifier + "'";
538                                 msoLogger.debug(msg);
539                                 fail(msg);
540                         }
541
542                         if (contentType == null) {
543                                 // Default for backward compatibility with existing tests.
544                                 contentType = JSON;
545                         }
546
547                         if (!injectSDNCRestCallback(contentType, content, 10000)) {
548                                 fail("Failed to inject SDNC '" + action + "' callback");
549                         }
550
551                         try {
552                                 Thread.sleep(1000);
553                         } catch (InterruptedException e) {
554                                 fail("Interrupted after injection of SDNC '" + action + "' callback");
555                         }
556                 }
557         }
558
559         /**
560          * Runs a program to inject SDNC events into the test environment.
561          * A program is essentially just a list of keys that identify event data
562          * to be injected, in sequence. An example program:
563          * <pre>
564          *     event1, event2
565          * </pre>
566          * NOTE: Each callback must have a message type associated with it, e.g.
567          * "SDNCAEvent".
568          * Errors are handled with junit assertions and will cause the test to fail.
569          * @param callbacks an object containing event data for the program
570          * @param program the program to execute
571          */
572         protected void injectSDNCEvents(CallbackSet callbacks, String program) {
573                 injectWorkflowMessages(callbacks, program);
574         }
575
576         /**
577          * Runs a program to inject SDNC callback data into the test environment.
578          * A program is essentially just a list of keys that identify callback data
579          * to be injected, in sequence. An example program:
580          * <pre>
581          *     reserve, assign, delete:ERR
582          * </pre>
583          * Errors are handled with junit assertions and will cause the test to fail.
584          * Uses the static/default timeout value for backward compatibility.
585          * @param callbacks an object containing callback data for the program
586          * @param program the program to execute
587          */
588         protected void injectSDNCCallbacks(CallbackSet callbacks, String program) {
589                 injectSDNCCallbacks(callbacks, program, timeout);
590         }
591
592         /**
593          * Runs a program to inject SDNC callback data into the test environment.
594          * A program is essentially just a list of keys that identify callback data
595          * to be injected, in sequence. An example program:
596          * <pre>
597          *     reserve, assign, delete:ERR
598          * </pre>
599          * Errors are handled with junit assertions and will cause the test to fail.
600          * @param callbacks an object containing callback data for the program
601          * @param program the program to execute
602          * @param timeout a timeout value to wait for the callback
603          */
604         protected void injectSDNCCallbacks(CallbackSet callbacks, String program, int timeout) {
605
606                 String[] cmds = program.replaceAll("\\s+", "").split(",");
607
608                 for (String cmd : cmds) {
609                         String action = cmd;
610                         String modifier = "STD";
611
612                         if (cmd.contains(":")) {
613                                 String[] parts = cmd.split(":");
614                                 action = parts[0];
615                                 modifier = parts[1];
616                         }
617
618                         String content = null;
619                         int respCode = 200;
620                         String respMsg = "OK";
621
622                         if ("STD".equals(modifier)) {
623                                 CallbackData callbackData = callbacks.get(action);
624
625                                 if (callbackData == null) {
626                                         String msg = "No callback defined for '" + action + "' SDNC request";
627                                         msoLogger.debug(msg);
628                                         fail(msg);
629                                 }
630
631                                 content = callbackData.getContent();
632                                 respCode = 200;
633                                 respMsg = "OK";
634                         } else if ("CREATED".equals(modifier)) {
635                                 CallbackData callbackData = callbacks.get(action);
636
637                                 if (callbackData == null) {
638                                         String msg = "No callback defined for '" + action + "' SDNC request";
639                                         msoLogger.debug(msg);
640                                         fail(msg);
641                                 }
642
643                                 content = callbackData.getContent();
644                                 respCode = 201;
645                                 respMsg = "Created";
646                         } else if ("ERR".equals(modifier)) {
647                                 content = "<svc-request-id>((REQUEST-ID))</svc-request-id><response-code>500</response-code><response-message>SIMULATED ERROR FROM SDNC ADAPTER</response-message>";
648                                 respCode = 500;
649                                 respMsg = "SERVER ERROR";
650                         } else {
651                                 String msg = "Invalid SDNC program modifier: '" + modifier + "'";
652                                 msoLogger.debug(msg);
653                                 fail(msg);
654                         }
655
656                         if (!injectSDNCCallback(respCode, respMsg, content, 10000)) {
657                                 fail("Failed to inject SDNC '" + action + "' callback");
658                         }
659
660                         try {
661                                 Thread.sleep(1000);
662                         } catch (InterruptedException e) {
663                                 fail("Interrupted after injection of SDNC '" + action + "' callback");
664                         }
665                 }
666         }
667
668         /**
669          * Runs a program to inject VNF adapter REST callback data into the test environment.
670          * A program is essentially just a list of keys that identify callback data
671          * to be injected, in sequence. An example program:
672          * <pre>
673          *     create, rollback
674          * </pre>
675          * Errors are handled with junit assertions and will cause the test to fail.
676          * @param callbacks an object containing callback data for the program
677          * @param program the program to execute
678          */
679         protected void injectVNFRestCallbacks(CallbackSet callbacks, String program) {
680
681                 String[] cmds = program.replaceAll("\\s+", "").split(",");
682
683                 for (String cmd : cmds) {
684                         String action = cmd;
685                         String modifier = "STD";
686
687                         if (cmd.contains(":")) {
688                                 String[] parts = cmd.split(":");
689                                 action = parts[0];
690                                 modifier = parts[1];
691                         }
692
693                         String content = null;
694                         String contentType = null;
695
696                         if ("STD".equals(modifier)) {
697                                 CallbackData callbackData = callbacks.get(action);
698
699                                 if (callbackData == null) {
700                                         String msg = "No callback defined for '" + action + "' VNF REST request";
701                                         msoLogger.debug(msg);
702                                         fail(msg);
703                                 }
704
705                                 content = callbackData.getContent();
706                                 contentType = callbackData.getContentType();
707                         } else if ("ERR".equals(modifier)) {
708                                 content = "SIMULATED ERROR FROM VNF ADAPTER";
709                                 contentType = "text/plain";
710                         } else {
711                                 String msg = "Invalid VNF REST program modifier: '" + modifier + "'";
712                                 msoLogger.debug(msg);
713                                 fail(msg);
714                         }
715
716                         if (contentType == null) {
717                                 // Default for backward compatibility with existing tests.
718                                 contentType = XML;
719                         }
720
721                         if (!injectVnfAdapterRestCallback(contentType, content, 10000)) {
722                                 fail("Failed to inject VNF REST '" + action + "' callback");
723                         }
724
725                         try {
726                                 Thread.sleep(1000);
727                         } catch (InterruptedException e) {
728                                 fail("Interrupted after injection of VNF REST '" + action + "' callback");
729                         }
730                 }
731         }
732
733         /**
734          * Runs a program to inject VNF callback data into the test environment.
735          * A program is essentially just a list of keys that identify callback data
736          * to be injected, in sequence. An example program:
737          * <pre>
738          *     createVnf, deleteVnf
739          * </pre>
740          * Errors are handled with junit assertions and will cause the test to fail.
741          * @param callbacks an object containing callback data for the program
742          * @param program the program to execute
743          */
744         protected void injectVNFCallbacks(CallbackSet callbacks, String program) {
745
746                 String[] cmds = program.replaceAll("\\s+", "").split(",");
747
748                 for (String cmd : cmds) {
749                         String action = cmd;
750                         String modifier = "STD";
751
752                         if (cmd.contains(":")) {
753                                 String[] parts = cmd.split(":");
754                                 action = parts[0];
755                                 modifier = parts[1];
756                         }
757
758                         String content = null;
759
760                         if ("STD".equals(modifier)) {
761                                 CallbackData callbackData = callbacks.get(action);
762
763                                 if (callbackData == null) {
764                                         String msg = "No callback defined for '" + action + "' VNF request";
765                                         msoLogger.debug(msg);
766                                         fail(msg);
767                                 }
768
769                                 content = callbackData.getContent();
770                         } else if ("ERR".equals(modifier)) {
771                                 String msg = "Currently unsupported VNF program modifier: '" + modifier + "'";
772                                 msoLogger.debug(msg);
773                                 fail(msg);
774                         } else {
775                                 String msg = "Invalid VNF program modifier: '" + modifier + "'";
776                                 msoLogger.debug(msg);
777                                 fail(msg);
778                         }
779
780                         boolean injected = false;
781
782                         if (content.contains("createVnfNotification")) {
783                                 injected = injectCreateVNFCallback(content, 10000);
784                         } else if (content.contains("deleteVnfNotification")) {
785                                 injected = injectDeleteVNFCallback(content, 10000);
786                         } else if (content.contains("updateVnfNotification")) {
787                                 injected = injectUpdateVNFCallback(content, 10000);
788                         }
789
790                         if (!injected) {
791                                 String msg = "Failed to inject VNF '" + action + "' callback";
792                                 msoLogger.debug(msg);
793                                 fail(msg);
794                         }
795
796                         try {
797                                 Thread.sleep(1000);
798                         } catch (InterruptedException e) {
799                                 fail("Interrupted after injection of VNF '" + action + "' callback");
800                         }
801                 }
802         }
803
804         /**
805          * Waits for the number of running processes with the specified process
806          * definition key to equal a particular count.
807          * @param processKey the process definition key
808          * @param count the desired count
809          * @param timeout the timeout in milliseconds
810          */
811         protected void waitForRunningProcessCount(String processKey, int count, long timeout) {
812                 msoLogger.debug("Waiting " + timeout + "ms for there to be " + count + " "
813                         + processKey + " instances");
814
815                 long now = System.currentTimeMillis() + timeout;
816                 long endTime = now + timeout;
817                 int last = -1;
818
819                 while (now <= endTime) {
820                         int actual = runtimeService
821                                 .createProcessInstanceQuery()
822                                 .processDefinitionKey(processKey)
823                                 .list().size();
824
825                         if (actual != last) {
826                                 msoLogger.debug("There are now " + actual + " "
827                                         + processKey + " instances");
828                                 last = actual;
829                         }
830
831                         if (actual == count) {
832                                 return;
833                         }
834
835                         try {
836                                 Thread.sleep(200);
837                         } catch (InterruptedException e) {
838                                 String msg = "Interrupted waiting for there to be " + count + " "
839                                         + processKey + " instances";
840                                 msoLogger.debug(msg);
841                                 fail(msg);
842                         }
843
844                         now = System.currentTimeMillis();
845                 }
846
847                 String msg = "Timed out waiting for there to be " + count + " "
848                         + processKey + " instances";
849                 msoLogger.debug(msg);
850                 fail(msg);
851         }
852
853         /**
854          * Waits for the specified process variable to be set.
855          * @param processKey the process definition key
856          * @param variable the variable name
857          * @param timeout the timeout in milliseconds
858          * @return the variable value, or null if it cannot be obtained
859          *         in the specified time
860          */
861         protected Object getProcessVariable(String processKey, String variable,
862                         long timeout) {
863
864                 msoLogger.debug("Waiting " + timeout + "ms for "
865                         + processKey + "." + variable + " to be set");
866
867                 long now = System.currentTimeMillis() + timeout;
868                 long endTime = now + timeout;
869
870                 ProcessInstance processInstance = null;
871                 Object value = null;
872
873                 while (value == null) {
874                         if (now > endTime) {
875                                 if (processInstance == null) {
876                                         msoLogger.debug("Timed out waiting for "
877                                                 + processKey + " to start");
878                                 } else {
879                                         msoLogger.debug("Timed out waiting for "
880                                                 + processKey + "[" + processInstance.getId()
881                                                 + "]." + variable + " to be set");
882                                 }
883
884                                 return null;
885                         }
886
887                         if (processInstance == null) {
888                                 processInstance = runtimeService
889                                         .createProcessInstanceQuery()
890                                         .processDefinitionKey(processKey)
891                                         .singleResult();
892                         }
893
894                         if (processInstance != null) {
895                                 value = runtimeService
896                                         .getVariable(processInstance.getId(), variable);
897                         }
898
899                         try {
900                                 Thread.sleep(200);
901                         } catch (InterruptedException e) {
902                                 msoLogger.debug("Interrupted waiting for "
903                                         + processKey + "." + variable + " to be set");
904                                 return null;
905                         }
906
907                         now = System.currentTimeMillis();
908                 }
909
910                 msoLogger.debug(processKey + "["
911                         + processInstance.getId() + "]." + variable + "="
912                         + value);
913
914                 return value;
915         }
916
917         /**
918          * Injects a single SDNC adapter callback request. The specified callback data
919          * may contain the placeholder string ((REQUEST-ID)) which is replaced with
920          * the actual SDNC request ID. Note: this is not the requestId in the original
921          * MSO request.
922          * @param contentType the HTTP content type for the callback
923          * @param content the content of the callback
924          * @param timeout the timeout in milliseconds
925          * @return true if the callback could be injected, false otherwise
926          */
927         protected boolean injectSDNCRestCallback(String contentType, String content, long timeout) {
928                 String sdncRequestId = (String) getProcessVariable("SDNCAdapterRestV1",
929                         "SDNCAResponse_CORRELATOR", timeout);
930                 
931                 if (sdncRequestId == null) {
932                         sdncRequestId = (String) getProcessVariable("SDNCAdapterRestV2",
933                                 "SDNCAResponse_CORRELATOR", timeout);
934                 }
935
936                 if (sdncRequestId == null) {
937                         return false;
938                 }
939
940                 content = content.replace("((REQUEST-ID))", sdncRequestId);
941                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
942                 content = content.replace("{{REQUEST-ID}}", sdncRequestId);
943
944                 msoLogger.debug("Injecting SDNC adapter callback");
945
946                 Response response = workflowMessageResource.deliver(contentType, "SDNCAResponse", sdncRequestId, content);
947                 msoLogger.debug("Workflow response to SDNC adapter callback: " + response);
948                 return true;
949         }
950
951         /**
952          * Injects a single SDNC adapter callback request. The specified callback data
953          * may contain the placeholder string ((REQUEST-ID)) which is replaced with
954          * the actual SDNC request ID. Note: this is not the requestId in the original
955          * MSO request.
956          * @param content the content of the callback
957          * @param respCode the response code (normally 200)
958          * @param respMsg the response message (normally "OK")
959          * @param timeout the timeout in milliseconds
960          * @return true if the callback could be injected, false otherwise
961          */
962         protected boolean injectSDNCCallback(int respCode, String respMsg,
963                         String content, long timeout) {
964
965                 String sdncRequestId = (String) getProcessVariable("sdncAdapter",
966                         "SDNCA_requestId", timeout);
967
968                 if (sdncRequestId == null) {
969                         return false;
970                 }
971
972                 content = content.replace("((REQUEST-ID))", sdncRequestId);
973                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
974                 content = content.replace("{{REQUEST-ID}}", sdncRequestId);
975
976                 // TODO this needs to be fixed. It is causing double tags and content
977                 // Need to parse content before setting below since content includes not just RequestData or modify callback files to only contain RequestData contents.
978
979                 msoLogger.debug("Injecting SDNC adapter callback");
980                 CallbackHeader callbackHeader = new CallbackHeader();
981                 callbackHeader.setRequestId(sdncRequestId);
982                 callbackHeader.setResponseCode(String.valueOf(respCode));
983                 callbackHeader.setResponseMessage(respMsg);
984                 SDNCAdapterCallbackRequest sdncAdapterCallbackRequest = new SDNCAdapterCallbackRequest();
985                 sdncAdapterCallbackRequest.setCallbackHeader(callbackHeader);
986                 sdncAdapterCallbackRequest.setRequestData(content);
987                 SDNCAdapterResponse sdncAdapterResponse = callbackService.sdncAdapterCallback(sdncAdapterCallbackRequest);
988                 msoLogger.debug("Workflow response to SDNC adapter callback: " + sdncAdapterResponse);
989
990                 return true;
991         }
992
993         /**
994          * Injects a single VNF adapter callback request. The specified callback data
995          * may contain the placeholder string ((MESSAGE-ID)) which is replaced with
996          * the actual message ID. Note: this is not the requestId in the original
997          * MSO request.
998          * @param contentType the HTTP content type for the callback
999          * @param content the content of the callback
1000          * @param timeout the timeout in milliseconds
1001          * @return true if the callback could be injected, false otherwise
1002          */
1003         protected boolean injectVnfAdapterRestCallback(String contentType, String content, long timeout) {
1004                 String messageId = (String) getProcessVariable("vnfAdapterRestV1",
1005                         "VNFAResponse_CORRELATOR", timeout);
1006
1007                 if (messageId == null) {
1008                         return false;
1009                 }
1010
1011                 content = content.replace("((MESSAGE-ID))", messageId);
1012                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1013                 content = content.replace("{{MESSAGE-ID}}", messageId);
1014
1015                 msoLogger.debug("Injecting VNF adapter callback");
1016
1017                 Response response = workflowMessageResource.deliver(contentType, "VNFAResponse", messageId, content);
1018                 msoLogger.debug("Workflow response to VNF adapter callback: " + response);
1019                 return true;
1020         }
1021
1022         /**
1023          * Injects a Create VNF adapter callback request. The specified callback data
1024          * may contain the placeholder string ((MESSAGE-ID)) which is replaced with
1025          * the actual message ID.  It may also contain the placeholder string
1026          * ((REQUEST-ID)) which is replaced request ID of the original MSO request.
1027          * @param content the content of the callback
1028          * @param timeout the timeout in milliseconds
1029          * @return true if the callback could be injected, false otherwise
1030          * @throws JAXBException if the content does not adhere to the schema
1031          */
1032         protected boolean injectCreateVNFCallback(String content, long timeout) {
1033
1034                 String messageId = (String) getProcessVariable("vnfAdapterCreateV1",
1035                         "VNFC_messageId", timeout);
1036
1037                 if (messageId == null) {
1038                         return false;
1039                 }
1040
1041                 content = content.replace("((MESSAGE-ID))", messageId);
1042                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1043                 content = content.replace("{{MESSAGE-ID}}", messageId);
1044
1045                 if(content.contains("((REQUEST-ID))")){
1046                         content = content.replace("((REQUEST-ID))", msoRequestId);
1047                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1048                         content = content.replace("{{REQUEST-ID}}", msoRequestId);
1049                 }
1050
1051                 msoLogger.debug("Injecting VNF adapter callback");
1052
1053                 // Is it possible to unmarshal this with JAXB?  I couldn't.
1054
1055                 CreateVnfNotification createVnfNotification = new CreateVnfNotification();
1056                 XPathTool xpathTool = new VnfNotifyXPathTool();
1057                 xpathTool.setXML(content);
1058
1059                 try {
1060                         String completed = xpathTool.evaluate(
1061                                 "/tns:createVnfNotification/tns:completed/text()");
1062                         createVnfNotification.setCompleted("true".equals(completed));
1063
1064                         String vnfId = xpathTool.evaluate(
1065                                 "/tns:createVnfNotification/tns:vnfId/text()");
1066                         createVnfNotification.setVnfId(vnfId);
1067
1068                         NodeList entries = (NodeList) xpathTool.evaluate(
1069                                 "/tns:createVnfNotification/tns:outputs/tns:entry",
1070                                 XPathConstants.NODESET);
1071
1072                         CreateVnfNotificationOutputs outputs = new CreateVnfNotificationOutputs();
1073
1074                         for (int i = 0; i < entries.getLength(); i++) {
1075                                 Node node = entries.item(i);
1076
1077                                 if (node.getNodeType() == Node.ELEMENT_NODE) {
1078                                         Element entry = (Element) node;
1079                                         String key = entry.getElementsByTagNameNS("*", "key").item(0).getTextContent();
1080                                         String value = entry.getElementsByTagNameNS("*", "value").item(0).getTextContent();
1081                                         outputs.add(key, value);
1082                                 }
1083                         }
1084
1085                         createVnfNotification.setOutputs(outputs);
1086
1087                         VnfRollback rollback = new VnfRollback();
1088
1089                         String cloudSiteId = xpathTool.evaluate(
1090                                 "/tns:createVnfNotification/tns:rollback/tns:cloudSiteId/text()");
1091                         rollback.setCloudSiteId(cloudSiteId);
1092
1093                         String requestId = xpathTool.evaluate(
1094                                 "/tns:createVnfNotification/tns:rollback/tns:msoRequest/tns:requestId/text()");
1095                         String serviceInstanceId = xpathTool.evaluate(
1096                                 "/tns:createVnfNotification/tns:rollback/tns:msoRequest/tns:serviceInstanceId/text()");
1097
1098                         if (requestId != null || serviceInstanceId != null) {
1099                                 MsoRequest msoRequest = new MsoRequest();
1100                                 msoRequest.setRequestId(requestId);
1101                                 msoRequest.setServiceInstanceId(serviceInstanceId);
1102                                 rollback.setMsoRequest(msoRequest);
1103                         }
1104
1105                         String tenantCreated = xpathTool.evaluate(
1106                                 "/tns:createVnfNotification/tns:rollback/tns:tenantCreated/text()");
1107                         rollback.setTenantCreated("true".equals(tenantCreated));
1108
1109                         String tenantId = xpathTool.evaluate(
1110                                 "/tns:createVnfNotification/tns:rollback/tns:tenantId/text()");
1111                         rollback.setTenantId(tenantId);
1112
1113                         String vnfCreated = xpathTool.evaluate(
1114                                 "/tns:createVnfNotification/tns:rollback/tns:vnfCreated/text()");
1115                         rollback.setVnfCreated("true".equals(vnfCreated));
1116
1117                         String rollbackVnfId = xpathTool.evaluate(
1118                                 "/tns:createVnfNotification/tns:rollback/tns:vnfId/text()");
1119                         rollback.setVnfId(rollbackVnfId);
1120
1121                         createVnfNotification.setRollback(rollback);
1122
1123                 } catch (Exception e) {
1124                         msoLogger.debug("Failed to unmarshal VNF callback content:");
1125                         msoLogger.debug(content);
1126                         return false;
1127                 }
1128
1129                 VnfAdapterNotifyServiceImpl notifyService = new VnfAdapterNotifyServiceImpl();
1130
1131
1132                 notifyService.createVnfNotification(
1133                         messageId,
1134                         createVnfNotification.isCompleted(),
1135                         createVnfNotification.getException(),
1136                         createVnfNotification.getErrorMessage(),
1137                         createVnfNotification.getVnfId(),
1138                         createVnfNotification.getOutputs(),
1139                         createVnfNotification.getRollback());
1140
1141                 return true;
1142         }
1143
1144         /**
1145          * Injects a Delete VNF adapter callback request. The specified callback data
1146          * may contain the placeholder string ((MESSAGE-ID)) which is replaced with
1147          * the actual message ID.  It may also contain the placeholder string
1148          * ((REQUEST-ID)) which is replaced request ID of the original MSO request.
1149          * @param content the content of the callback
1150          * @param timeout the timeout in milliseconds
1151          * @return true if the callback could be injected, false otherwise
1152          * @throws JAXBException if the content does not adhere to the schema
1153          */
1154         protected boolean injectDeleteVNFCallback(String content, long timeout) {
1155
1156                 String messageId = (String) getProcessVariable("vnfAdapterDeleteV1",
1157                         "VNFDEL_uuid", timeout);
1158
1159                 if (messageId == null) {
1160                         return false;
1161                 }
1162
1163                 content = content.replace("((MESSAGE-ID))", messageId);
1164                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1165                 content = content.replace("{{MESSAGE-ID}}", messageId);
1166
1167                 msoLogger.debug("Injecting VNF adapter delete callback");
1168
1169                 // Is it possible to unmarshal this with JAXB?  I couldn't.
1170
1171                 DeleteVnfNotification deleteVnfNotification = new DeleteVnfNotification();
1172                 XPathTool xpathTool = new VnfNotifyXPathTool();
1173                 xpathTool.setXML(content);
1174
1175                 try {
1176                         String completed = xpathTool.evaluate(
1177                                 "/tns:deleteVnfNotification/tns:completed/text()");
1178                         deleteVnfNotification.setCompleted("true".equals(completed));
1179                         // if notification failure, set the exception and error message
1180                         if (deleteVnfNotification.isCompleted() == false) {
1181                                 deleteVnfNotification.setException(MsoExceptionCategory.INTERNAL);
1182                                 deleteVnfNotification.setErrorMessage(xpathTool.evaluate(
1183                                                 "/tns:deleteVnfNotification/tns:errorMessage/text()")) ;
1184                         }
1185
1186                 } catch (Exception e) {
1187                         msoLogger.debug("Failed to unmarshal VNF Delete callback content:");
1188                         msoLogger.debug(content);
1189                         return false;
1190                 }
1191
1192                 VnfAdapterNotifyServiceImpl notifyService = new VnfAdapterNotifyServiceImpl();
1193
1194
1195                 notifyService.deleteVnfNotification(
1196                         messageId,
1197                         deleteVnfNotification.isCompleted(),
1198                         deleteVnfNotification.getException(),
1199                         deleteVnfNotification.getErrorMessage());
1200
1201                 return true;
1202         }
1203
1204         /**
1205          * Injects a Update VNF adapter callback request. The specified callback data
1206          * may contain the placeholder string ((MESSAGE-ID)) which is replaced with
1207          * the actual message ID.  It may also contain the placeholder string
1208          * ((REQUEST-ID)) which is replaced request ID of the original MSO request.
1209          * @param content the content of the callback
1210          * @param timeout the timeout in milliseconds
1211          * @return true if the callback could be injected, false otherwise
1212          * @throws JAXBException if the content does not adhere to the schema
1213          */
1214         protected boolean injectUpdateVNFCallback(String content, long timeout) {
1215
1216                 String messageId = (String) getProcessVariable("vnfAdapterUpdate",
1217                         "VNFU_messageId", timeout);
1218
1219                 if (messageId == null) {
1220                         return false;
1221                 }
1222
1223                 content = content.replace("((MESSAGE-ID))", messageId);
1224                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1225                 content = content.replace("{{MESSAGE-ID}}", messageId);
1226
1227                 content = content.replace("((REQUEST-ID))", msoRequestId);
1228                 // Deprecated usage.  All test code should switch to the (( ... )) syntax.
1229                 content = content.replace("{{REQUEST-ID}}", msoRequestId);
1230
1231                 msoLogger.debug("Injecting VNF adapter callback");
1232
1233                 // Is it possible to unmarshal this with JAXB?  I couldn't.
1234
1235                 UpdateVnfNotification updateVnfNotification = new UpdateVnfNotification();
1236                 XPathTool xpathTool = new VnfNotifyXPathTool();
1237                 xpathTool.setXML(content);
1238
1239                 try {
1240                         String completed = xpathTool.evaluate(
1241                                 "/tns:updateVnfNotification/tns:completed/text()");
1242                         updateVnfNotification.setCompleted("true".equals(completed));
1243
1244                         NodeList entries = (NodeList) xpathTool.evaluate(
1245                                 "/tns:updateVnfNotification/tns:outputs/tns:entry",
1246                                 XPathConstants.NODESET);
1247
1248                         UpdateVnfNotificationOutputs outputs = new UpdateVnfNotificationOutputs();
1249
1250                         for (int i = 0; i < entries.getLength(); i++) {
1251                                 Node node = entries.item(i);
1252
1253                                 if (node.getNodeType() == Node.ELEMENT_NODE) {
1254                                         Element entry = (Element) node;
1255                                         String key = entry.getElementsByTagNameNS("*", "key").item(0).getTextContent();
1256                                         String value = entry.getElementsByTagNameNS("*", "value").item(0).getTextContent();
1257                                         outputs.add(key, value);
1258                                 }
1259                         }
1260
1261                         updateVnfNotification.setOutputs(outputs);
1262
1263                         VnfRollback rollback = new VnfRollback();
1264
1265                         String cloudSiteId = xpathTool.evaluate(
1266                                 "/tns:updateVnfNotification/tns:rollback/tns:cloudSiteId/text()");
1267                         rollback.setCloudSiteId(cloudSiteId);
1268
1269                         String requestId = xpathTool.evaluate(
1270                                 "/tns:updateVnfNotification/tns:rollback/tns:msoRequest/tns:requestId/text()");
1271                         String serviceInstanceId = xpathTool.evaluate(
1272                                 "/tns:updateVnfNotification/tns:rollback/tns:msoRequest/tns:serviceInstanceId/text()");
1273
1274                         if (requestId != null || serviceInstanceId != null) {
1275                                 MsoRequest msoRequest = new MsoRequest();
1276                                 msoRequest.setRequestId(requestId);
1277                                 msoRequest.setServiceInstanceId(serviceInstanceId);
1278                                 rollback.setMsoRequest(msoRequest);
1279                         }
1280
1281                         String tenantCreated = xpathTool.evaluate(
1282                                 "/tns:updateVnfNotification/tns:rollback/tns:tenantCreated/text()");
1283                         rollback.setTenantCreated("true".equals(tenantCreated));
1284
1285                         String tenantId = xpathTool.evaluate(
1286                                 "/tns:updateVnfNotification/tns:rollback/tns:tenantId/text()");
1287                         rollback.setTenantId(tenantId);
1288
1289                         String vnfCreated = xpathTool.evaluate(
1290                                 "/tns:updateVnfNotification/tns:rollback/tns:vnfCreated/text()");
1291                         rollback.setVnfCreated("true".equals(vnfCreated));
1292
1293                         String rollbackVnfId = xpathTool.evaluate(
1294                                 "/tns:updateVnfNotification/tns:rollback/tns:vnfId/text()");
1295                         rollback.setVnfId(rollbackVnfId);
1296
1297                         updateVnfNotification.setRollback(rollback);
1298
1299                 } catch (Exception e) {
1300                         msoLogger.debug("Failed to unmarshal VNF callback content:");
1301                         msoLogger.debug(content);
1302                         return false;
1303                 }
1304
1305                 VnfAdapterNotifyServiceImpl notifyService = new VnfAdapterNotifyServiceImpl();
1306
1307
1308                 notifyService.updateVnfNotification(
1309                         messageId,
1310                         updateVnfNotification.isCompleted(),
1311                         updateVnfNotification.getException(),
1312                         updateVnfNotification.getErrorMessage(),
1313                         updateVnfNotification.getOutputs(),
1314                         updateVnfNotification.getRollback());
1315
1316                 return true;
1317         }
1318
1319         /**
1320          * Runs a program to inject workflow messages into the test environment.
1321          * A program is essentially just a list of keys that identify event data
1322          * to be injected, in sequence. An example program:
1323          * <pre>
1324          *     event1, event2
1325          * </pre>
1326          * Errors are handled with junit assertions and will cause the test to fail.
1327          * NOTE: Each callback must have a workflow message type associated with it.
1328          * @param callbacks an object containing event data for the program
1329          * @param program the program to execute
1330          */
1331         protected void injectWorkflowMessages(CallbackSet callbacks, String program) {
1332
1333                 String[] cmds = program.replaceAll("\\s+", "").split(",");
1334
1335                 for (String cmd : cmds) {
1336                         String action = cmd;
1337                         String modifier = "STD";
1338
1339                         if (cmd.contains(":")) {
1340                                 String[] parts = cmd.split(":");
1341                                 action = parts[0];
1342                                 modifier = parts[1];
1343                         }
1344
1345                         String messageType = null;
1346                         String content = null;
1347                         String contentType = null;
1348
1349                         if ("STD".equals(modifier)) {
1350                                 CallbackData callbackData = callbacks.get(action);
1351
1352                                 if (callbackData == null) {
1353                                         String msg = "No '" + action + "' workflow message callback is defined";
1354                                         msoLogger.debug(msg);
1355                                         fail(msg);
1356                                 }
1357
1358                                 messageType = callbackData.getMessageType();
1359
1360                                 if (messageType == null || messageType.trim().equals("")) {
1361                                         String msg = "No workflow message type is defined in the '" + action + "' callback";
1362                                         msoLogger.debug(msg);
1363                                         fail(msg);
1364                                 }
1365
1366                                 content = callbackData.getContent();
1367                                 contentType = callbackData.getContentType();
1368                         } else {
1369                                 String msg = "Invalid workflow message program modifier: '" + modifier + "'";
1370                                 msoLogger.debug(msg);
1371                                 fail(msg);
1372                         }
1373
1374                         if (!injectWorkflowMessage(contentType, messageType, content, 10000)) {
1375                                 fail("Failed to inject '" + action + "' workflow message");
1376                         }
1377
1378                         try {
1379                                 Thread.sleep(1000);
1380                         } catch (InterruptedException e) {
1381                                 fail("Interrupted after injection of '" + action + "' workflow message");
1382                         }
1383                 }
1384         }
1385
1386         /**
1387          * Injects a workflow message. The specified callback data may contain the
1388          * placeholder string ((CORRELATOR)) which is replaced with the actual
1389          * correlator value.
1390          * @param contentType the HTTP contentType for the message (possibly null)
1391          * @param messageType the message type
1392          * @param content the message content (possibly null)
1393          * @param timeout the timeout in milliseconds
1394          * @return true if the message could be injected, false otherwise
1395          */
1396         protected boolean injectWorkflowMessage(String contentType, String messageType, String content, long timeout) {
1397                 String correlator = (String) getProcessVariable("ReceiveWorkflowMessage",
1398                         messageType + "_CORRELATOR", timeout);
1399
1400                 if (correlator == null) {
1401                         return false;
1402                 }
1403
1404                 if (content != null) {
1405                         content = content.replace("((CORRELATOR))", correlator);
1406                 }
1407
1408                 msoLogger.debug("Injecting " + messageType + " message");
1409
1410                 Response response = workflowMessageResource.deliver(contentType, messageType, correlator, content);
1411                 msoLogger.debug("Workflow response to " + messageType + " message: " + response);
1412                 return true;
1413         }
1414
1415         /**
1416          * Runs a program to inject sniro workflow messages into the test environment.
1417          * A program is essentially just a list of keys that identify event data
1418          * to be injected, in sequence. For more details, see
1419          * injectSNIROCallbacks(String contentType, String messageType, String content, long timeout)
1420          *
1421          * Errors are handled with junit assertions and will cause the test to fail.
1422          * NOTE: Each callback must have a workflow message type associated with it.
1423          *
1424          * @param callbacks an object containing event data for the program
1425          * @param program the program to execute
1426          */
1427         protected void injectSNIROCallbacks(CallbackSet callbacks, String program) {
1428
1429                 String[] cmds = program.replaceAll("\\s+", "").split(",");
1430
1431                 for (String cmd : cmds) {
1432                         String action = cmd;
1433                         String modifier = "STD";
1434
1435                         if (cmd.contains(":")) {
1436                                 String[] parts = cmd.split(":");
1437                                 action = parts[0];
1438                                 modifier = parts[1];
1439                         }
1440
1441                         String messageType = null;
1442                         String content = null;
1443                         String contentType = null;
1444
1445                         if ("STD".equals(modifier)) {
1446                                 CallbackData callbackData = callbacks.get(action);
1447
1448                                 if (callbackData == null) {
1449                                         String msg = "No '" + action + "' workflow message callback is defined";
1450                                         msoLogger.debug(msg);
1451                                         fail(msg);
1452                                 }
1453
1454                                 messageType = callbackData.getMessageType();
1455
1456                                 if (messageType == null || messageType.trim().equals("")) {
1457                                         String msg = "No workflow message type is defined in the '" + action + "' callback";
1458                                         msoLogger.debug(msg);
1459                                         fail(msg);
1460                                 }
1461
1462                                 content = callbackData.getContent();
1463                                 contentType = callbackData.getContentType();
1464                         } else {
1465                                 String msg = "Invalid workflow message program modifier: '" + modifier + "'";
1466                                 msoLogger.debug(msg);
1467                                 fail(msg);
1468                         }
1469
1470                         if (!injectSNIROCallbacks(contentType, messageType, content, 10000)) {
1471                                 fail("Failed to inject '" + action + "' workflow message");
1472                         }
1473
1474                         try {
1475                                 Thread.sleep(1000);
1476                         } catch (InterruptedException e) {
1477                                 fail("Interrupted after injection of '" + action + "' workflow message");
1478                         }
1479                 }
1480         }
1481
1482         /**
1483          * Injects a sniro workflow message. The specified callback response may
1484          * contain the placeholder strings ((CORRELATOR)) and ((SERVICE_RESOURCE_ID))
1485          * The ((CORRELATOR)) is replaced with the actual correlator value from the
1486          * request. The ((SERVICE_RESOURCE_ID)) is replaced with the actual serviceResourceId
1487          * value from the sniro request. Currently this only works with sniro request
1488          * that contain only 1 resource.
1489          *
1490          * @param contentType the HTTP contentType for the message (possibly null)
1491          * @param messageType the message type
1492          * @param content the message content (possibly null)
1493          * @param timeout the timeout in milliseconds
1494          * @return true if the message could be injected, false otherwise
1495          */
1496         protected boolean injectSNIROCallbacks(String contentType, String messageType, String content, long timeout) {
1497                 String correlator = (String) getProcessVariable("ReceiveWorkflowMessage",
1498                         messageType + "_CORRELATOR", timeout);
1499
1500                 if (correlator == null) {
1501                         return false;
1502                 }
1503                 if (content != null) {
1504                         content = content.replace("((CORRELATOR))", correlator);
1505                         if(messageType.equalsIgnoreCase("SNIROResponse")){
1506                                 ServiceDecomposition decomp = (ServiceDecomposition) getProcessVariable("Homing", "serviceDecomposition", timeout);
1507                                 List<Resource> resourceList = decomp.getServiceResources();
1508                                 if(resourceList.size() == 1){
1509                                         String resourceId = "";
1510                                         for(Resource resource:resourceList){
1511                                                 resourceId = resource.getResourceId();
1512                                         }
1513                                         String homingList = getJsonValue(content, "solutionInfo.placementInfo");
1514                                         JSONArray placementArr = null;
1515                                         try {
1516                                                 placementArr = new JSONArray(homingList);
1517                                         }
1518                                         catch (Exception e) {
1519                                                 return false;
1520                                         }
1521                                         if(placementArr.length() == 1){
1522                                                 content = content.replace("((SERVICE_RESOURCE_ID))", resourceId);
1523                                         }
1524                                         String licenseInfoList = getJsonValue(content, "solutionInfo.licenseInfo");
1525                                         JSONArray licenseArr = null;
1526                                         try {
1527                                                 licenseArr = new JSONArray(licenseInfoList);
1528                                         }
1529                                         catch (Exception e) {
1530                                                 return false;
1531                                         }
1532                                         if(licenseArr.length() == 1){
1533                                                 content = content.replace("((SERVICE_RESOURCE_ID))", resourceId);
1534                                         }
1535                                 }
1536                                 else {
1537                                         try {
1538                                                 String homingList = getJsonValue(content, "solutionInfo.placementInfo");
1539                                                 String licenseInfoList = getJsonValue(content, "solutionInfo.licenseInfo");
1540                                                 JSONArray placementArr = new JSONArray(homingList);
1541                                                 JSONArray licenseArr = new JSONArray(licenseInfoList);
1542                                                 for (Resource resource: resourceList) {
1543                                                         String resourceModuleName = resource.getModelInfo().getModelInstanceName();
1544                                                         String resourceId = resource.getResourceId();
1545
1546                                                         for (int i=0; i<placementArr.length(); i++) {
1547                                                                 JSONObject placementObj = placementArr.getJSONObject(i);
1548                                                                 String placementModuleName = placementObj.getString("resourceModuleName");
1549                                                                 if (placementModuleName.equalsIgnoreCase(resourceModuleName)) {
1550                                                                         String placementString = placementObj.toString();
1551                                                                         placementString = placementString.replace("((SERVICE_RESOURCE_ID))", resourceId);
1552                                                                         JSONObject newPlacementObj = new JSONObject(placementString);
1553                                                                         placementArr.put(i, newPlacementObj);
1554                                                                 }
1555                                                         }
1556
1557                                                         for (int i=0; i<licenseArr.length(); i++) {
1558                                                                 JSONObject licenseObj = licenseArr.getJSONObject(i);
1559                                                                 String licenseModuleName = licenseObj.getString("resourceModuleName");
1560                                                                 if (licenseModuleName.equalsIgnoreCase(resourceModuleName)) {
1561                                                                         String licenseString = licenseObj.toString();
1562                                                                         licenseString = licenseString.replace("((SERVICE_RESOURCE_ID))", resourceId);
1563                                                                         JSONObject newLicenseObj = new JSONObject(licenseString);
1564                                                                         licenseArr.put(i, newLicenseObj);
1565                                                                 }
1566                                                         }
1567                                                 }
1568                                                 String newPlacementInfos = placementArr.toString();
1569                                                 String newLicenseInfos = licenseArr.toString();
1570                                                 content = updJsonValue(content, "solutionInfo.placementInfo", newPlacementInfos);
1571                                                 content = updJsonValue(content, "solutionInfo.licenseInfo", newLicenseInfos);
1572                                         }
1573                                         catch(Exception e) {
1574                                                 return false;
1575                                         }
1576
1577                                 }
1578                         }
1579                 }
1580                 msoLogger.debug("Injecting " + messageType + " message");
1581
1582                 Response response = workflowMessageResource.deliver(contentType, messageType, correlator, content);
1583                 msoLogger.debug("Workflow response to " + messageType + " message: " + response);
1584                 return true;
1585         }
1586
1587
1588         /**
1589          * Wait for the process to end.
1590          * @param businessKey the process business key
1591          * @param timeout the amount of time to wait, in milliseconds
1592          */
1593         protected void waitForProcessEnd(String businessKey, long timeout) {
1594                 msoLogger.debug("Waiting " + timeout + "ms for process with business key " +
1595                         businessKey + " to end");
1596
1597                 long now = System.currentTimeMillis() + timeout;
1598                 long endTime = now + timeout;
1599
1600                 while (now <= endTime) {
1601                         if (isProcessEnded(businessKey)) {
1602                                 msoLogger.debug("Process with business key " + businessKey + " has ended");
1603                                 return;
1604                         }
1605
1606                         try {
1607                                 Thread.sleep(200);
1608                         } catch (InterruptedException e) {
1609                                 String msg = "Interrupted waiting for process with business key " +
1610                                         businessKey + " to end";
1611                                 msoLogger.debug(msg);
1612                                 fail(msg);
1613                         }
1614
1615                         now = System.currentTimeMillis();
1616                 }
1617
1618                 String msg = "Process with business key " + businessKey +
1619                         " did not end within " + timeout + "ms";
1620                 msoLogger.debug(msg);
1621                 fail(msg);
1622         }
1623
1624         /**
1625          * Wait for the process to end. Must be used when multiple process instances exist with
1626          * this same business key such as when its passed to subflows or shared across multiple
1627          * processes.
1628          *
1629          * @param businessKey the process business key
1630          * @param processName the process definition name
1631          * @param timeout the amount of time to wait, in milliseconds
1632          * @author cb645j
1633          */
1634         protected void waitForProcessEnd(String businessKey, String processName, long timeout) {
1635                 msoLogger.debug("Waiting " + timeout + "ms for process with business key " +
1636                         businessKey + " to end");
1637
1638                 long now = System.currentTimeMillis() + timeout;
1639                 long endTime = now + timeout;
1640
1641                 while (now <= endTime) {
1642                         if (isProcessEnded(businessKey, processName)) {
1643                                 msoLogger.debug("Process with business key " + businessKey + " has ended");
1644                                 return;
1645                         }
1646
1647                         try {
1648                                 Thread.sleep(200);
1649                         } catch (InterruptedException e) {
1650                                 String msg = "Interrupted waiting for process with business key " +
1651                                         businessKey + " to end";
1652                                 msoLogger.debug(msg);
1653                                 fail(msg);
1654                         }
1655
1656                         now = System.currentTimeMillis();
1657                 }
1658
1659                 String msg = "Process with business key " + businessKey +
1660                         " did not end within " + timeout + "ms";
1661                 msoLogger.debug(msg);
1662                 fail(msg);
1663         }
1664
1665         /**
1666          * Verifies that the specified historic process variable has the specified value.
1667          * If the variable does not have the specified value, the test is failed.
1668          *
1669          * @param businessKey the process business key
1670          * @param variable the variable name
1671          * @param value the expected variable value
1672          */
1673         protected void checkVariable(String businessKey, String variable, Object value) {
1674                 if (!isProcessEnded(businessKey)) {
1675                         fail("Cannot get historic variable " + variable + " because process with business key " +
1676                                 businessKey + " has not ended");
1677                 }
1678
1679                 Object variableValue = getVariableFromHistory(businessKey, variable);
1680                 assertEquals(value, variableValue);
1681         }
1682
1683         /**
1684          * Checks to see if the specified process is ended.
1685          * @param businessKey the process business Key
1686          * @return true if the process is ended
1687          */
1688         protected boolean isProcessEnded(String businessKey) {
1689                 HistoricProcessInstance processInstance = historyService
1690                         .createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
1691                 return processInstance != null && processInstance.getEndTime() != null;
1692         }
1693
1694         /**
1695          * Checks to see if the specified process is ended.
1696          * 
1697          * @param processInstanceId the process Instance Id
1698          * @return true if the process is ended
1699          */
1700         protected boolean isProcessEndedByProcessInstanceId(String processInstanceId) {
1701                 HistoricProcessInstance processInstance = historyService
1702                                 .createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
1703                 return processInstance != null && processInstance.getEndTime() != null;
1704         }
1705
1706         /**
1707          * Checks to see if the specified process is ended.
1708          * 
1709          * @author cb645j
1710          */
1711         //TODO combine into 1
1712         private boolean isProcessEnded(String businessKey, String processName) {
1713                 HistoricProcessInstance processInstance = historyService
1714                         .createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).processDefinitionName(processName).singleResult();
1715                 return processInstance != null && processInstance.getEndTime() != null;
1716         }
1717
1718         /**
1719          * Gets a variable value from a historical process instance. The business key must be unique.
1720          *
1721          * @param businessKey the process business key
1722          * @param variableName the variable name
1723          * @return the variable value or null if the variable does not exist
1724          */
1725         protected Object getVariableFromHistory(String businessKey, String variableName) {
1726                 try {
1727                         HistoricProcessInstance processInstance = historyService
1728                                 .createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
1729
1730                         if (processInstance == null) {
1731                                 return null;
1732                         }
1733
1734                         HistoricVariableInstance v = historyService
1735                                 .createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId())
1736                                 .variableName(variableName).singleResult();
1737                         return v == null ? null : v.getValue();
1738                 } catch (Exception e) {
1739                         msoLogger.debug("Error retrieving variable " + variableName +
1740                                 " from historical process with business key " + businessKey + ": " + e);
1741                         return null;
1742                 }
1743         }
1744
1745         /**
1746          * Gets a variable value from a process instance based on businessKey and process name.
1747          * Must be used when multiple instances exist with the same business key such as when
1748          * business key is passed to subflows or shared across multiple processes. This method
1749          * can obtain variables from mainflows and from subflows.
1750          *
1751          * @param businessKey the process business key
1752          * @param processName the process definition name
1753          * @param variableName the variable name
1754          * @return the variable value or null if the variable does not exist
1755          * @author cb645j
1756          */
1757         protected Object getVariableFromHistory(String businessKey, String processName, String variableName){
1758                 try{
1759                         HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).processDefinitionName(processName)
1760                                         .singleResult();
1761
1762                         if(processInstance == null){
1763                                 return null;
1764                         }
1765                         HistoricVariableInstance variable = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
1766
1767                         return variable == null ? null : variable.getValue();
1768                 }catch(ProcessEngineException e){
1769                         msoLogger.debug("Multiple proccess instances exist with process name " + processName + " and business key " + businessKey + ". Must pass instance index as a parameter.");
1770                         return null;
1771                 }catch(Exception e){
1772                         msoLogger.debug("Error retrieving variable " + variableName + " from historical process for process " + processName + " with business key " + businessKey + ": " + e);
1773                         return null;
1774                 }
1775         }
1776
1777         /**
1778          * Gets the value of a process variable from x instance of y process. Must be used when
1779          * multiple instances exist with the same business key AND process name. This method
1780          * shall be used primarily for obtaining subflow variables when the business key is
1781          * passed to the subflow AND the subflow is called multiple times in a given flow.
1782          *
1783          * @param businessKey the process business key
1784          * @param processName the name of the subflow that contains the variable
1785          * @param variableName the variable name
1786          * @param processInstanceIndex the instance in which the subprocess was called
1787          * @return the variable value or null if the variable does not exist
1788          * @author cb645j
1789          */
1790         protected Object getVariableFromHistory(String businessKey, int subflowInstanceIndex, String processName, String variableName){
1791                 try{
1792                         List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).processDefinitionName(processName)
1793                                         .list();
1794
1795                         if(processInstanceList == null){
1796                                 return null;
1797                         }
1798                         processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
1799
1800                         HistoricProcessInstance processInstance = processInstanceList.get(subflowInstanceIndex);
1801                         HistoricVariableInstance variable = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId())
1802                                         .variableName(variableName).singleResult();
1803
1804                         return variable == null ? null : variable.getValue();
1805                 }catch(Exception e) {
1806                         msoLogger.debug("Error retrieving variable " + variableName + " from historical process for process " + processName + " with business key " + businessKey + ": " + e);
1807                         return null;
1808                 }
1809         }
1810
1811
1812         /**
1813          * Gets the value of a subflow variable from the specified subflow's
1814          * historical process instance.
1815          *
1816          * DEPRECATED - Use method getVariableFromHistory(businessKey, processName, variableName) instead
1817          *
1818          * @param subflowName - the name of the subflow that contains the variable
1819          * @param variableName the variable name
1820          *
1821          * @return the variable value, or null if the variable could not be obtained
1822          *
1823          */
1824         @Deprecated
1825         protected Object getVariableFromSubflowHistory(String subflowName, String variableName) {
1826                 try {
1827                         List<HistoricProcessInstance> processInstanceList = historyService
1828                                         .createHistoricProcessInstanceQuery().processDefinitionName(subflowName).list();
1829
1830                         if (processInstanceList == null) {
1831                                 return null;
1832                         }
1833
1834                         processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
1835
1836                         HistoricProcessInstance processInstance = processInstanceList.get(0);
1837
1838                         HistoricVariableInstance v = historyService
1839                                 .createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId())
1840                                 .variableName(variableName).singleResult();
1841                         return v == null ? null : v.getValue();
1842                 } catch (Exception e) {
1843                         msoLogger.debug("Error retrieving variable " + variableName +
1844                                         " from sub flow: " + subflowName + ", Exception is: " + e);
1845                         return null;
1846                 }
1847         }
1848
1849         /**
1850          * Gets the value of a subflow variable from the subflow's
1851          * historical process x instance.
1852          *
1853          * DEPRECATED: Use method getVariableFromHistory(businessKey, processInstanceIndex, processName, variableName) instead
1854          *
1855          * @param subflowName - the name of the subflow that contains the variable
1856          * @param variableName the variable name
1857          * @param subflowInstanceIndex - the instance of the subflow (use when same subflow is called more than once from mainflow)
1858          *
1859          * @return the variable value, or null if the variable could not be obtained
1860          */
1861         @Deprecated
1862         protected Object getVariableFromSubflowHistory(int subflowInstanceIndex, String subflowName, String variableName) {
1863                 try {
1864                         List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processDefinitionName(subflowName).list();
1865
1866                         if (processInstanceList == null) {
1867                                 return null;
1868                         }
1869
1870                         processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
1871
1872                         HistoricProcessInstance processInstance = processInstanceList.get(subflowInstanceIndex);
1873
1874                         HistoricVariableInstance v = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId())
1875                                 .variableName(variableName).singleResult();
1876                         return v == null ? null : v.getValue();
1877                 } catch (Exception e) {
1878                         msoLogger.debug("Error retrieving variable " + variableName +
1879                                 " from " + subflowInstanceIndex + " instance index of sub flow: " + subflowName + ", Exception is: " + e);
1880                         return null;
1881                 }
1882         }
1883
1884         /**
1885          * Extracts text from an XML element. This method is not namespace aware
1886          * (namespaces are ignored).  The first matching element is selected.
1887          * @param xml the XML document or fragment
1888          * @param tag the desired element, e.g. "<name>"
1889          * @return the element text, or null if the element was not found
1890          */
1891         protected String getXMLTextElement(String xml, String tag) {
1892                 xml = removeXMLNamespaces(xml);
1893
1894                 if (!tag.startsWith("<")) {
1895                         tag = "<" + tag + ">";
1896                 }
1897
1898                 int start = xml.indexOf(tag);
1899
1900                 if (start == -1) {
1901                         return null;
1902                 }
1903
1904                 int end = xml.indexOf('<', start + tag.length());
1905
1906                 if (end == -1) {
1907                         return null;
1908                 }
1909
1910                 return xml.substring(start + tag.length(), end);
1911         }
1912
1913         /**
1914          * Removes namespace definitions and prefixes from XML, if any.
1915          */
1916         private String removeXMLNamespaces(String xml) {
1917                 // remove xmlns declaration
1918                 xml = xml.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
1919
1920                 // remove opening tag prefix
1921                 xml = xml.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
1922
1923                 // remove closing tags prefix
1924                 xml = xml.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
1925
1926                 // remove extra spaces left when xmlns declarations are removed
1927                 xml = xml.replaceAll("\\s+>", ">");
1928
1929                 return xml;
1930         }
1931
1932         /**
1933          * Asserts that two XML documents are semantically equivalent.  Differences
1934          * in whitespace or in namespace usage do not affect the comparison.
1935          * @param expected the expected XML
1936          * @param actual the XML to test
1937          * @throws SAXException
1938          * @throws IOException
1939          */
1940     public static void assertXMLEquals(String expected, String actual)
1941                 throws SAXException, IOException {
1942         XMLUnit.setIgnoreWhitespace(true);
1943         XMLUnit.setIgnoreAttributeOrder(true);
1944         DetailedDiff diff = new DetailedDiff(XMLUnit.compareXML(expected, actual));
1945         List<?> allDifferences = diff.getAllDifferences();
1946         assertEquals("Differences found: " + diff.toString(), 0, allDifferences.size());
1947     }
1948
1949         /**
1950          * A test implementation of AsynchronousResponse.
1951          */
1952         public class TestAsyncResponse {
1953                 Response response = null;
1954
1955                 /**
1956                  * {@inheritDoc}
1957                  */
1958                 public synchronized void setResponse(Response response) {
1959                         this.response = response;
1960                 }
1961
1962                 /**
1963                  * Gets the response.
1964                  * @return the response, or null if none has been produced yet
1965                  */
1966                 public synchronized Response getResponse() {
1967                         return response;
1968                 }
1969         }
1970
1971         /**
1972          * An object that contains callback data for a "program".
1973          */
1974         public class CallbackSet {
1975                 private final Map<String, CallbackData> map = new HashMap<>();
1976
1977                 /**
1978                  * Add untyped callback data to the set.
1979                  * @param action the action with which the data is associated
1980                  * @param content the callback data
1981                  */
1982                 public void put(String action, String content) {
1983                         map.put(action, new CallbackData(null, null, content));
1984                 }
1985
1986                 /**
1987                  * Add callback data to the set.
1988                  * @param action the action with which the data is associated
1989                  * @param messageType the callback message type
1990                  * @param content the callback data
1991                  */
1992                 public void put(String action, String messageType, String content) {
1993                         map.put(action, new CallbackData(null, messageType, content));
1994                 }
1995
1996                 /**
1997                  * Add callback data to the set.
1998                  * @param action the action with which the data is associated
1999                  * @param contentType the callback HTTP content type
2000                  * @param messageType the callback message type
2001                  * @param content the callback data
2002                  */
2003                 public void put(String action, String contentType, String messageType, String content) {
2004                         map.put(action, new CallbackData(contentType, messageType, content));
2005                 }
2006
2007                 /**
2008                  * Retrieve callback data from the set.
2009                  * @param action the action with which the data is associated
2010                  * @return the callback data, or null if there is none for the specified operation
2011                  */
2012                 public CallbackData get(String action) {
2013                         return map.get(action);
2014                 }
2015         }
2016
2017         /**
2018          * Represents a callback data item.
2019          */
2020         public class CallbackData {
2021                 private final String contentType;
2022                 private final String messageType;
2023                 private final String content;
2024
2025                 /**
2026                  * Constructor
2027                  * @param contentType the HTTP content type (optional)
2028                  * @param messageType the callback message type (optional)
2029                  * @param content the content
2030                  */
2031                 public CallbackData(String contentType, String messageType, String content) {
2032                         this.contentType = contentType;
2033                         this.messageType = messageType;
2034                         this.content = content;
2035                 }
2036
2037                 /**
2038                  * Gets the callback HTTP content type, possibly null.
2039                  */
2040                 public String getContentType() {
2041                         return contentType;
2042                 }
2043
2044                 /**
2045                  * Gets the callback message type, possibly null.
2046                  */
2047                 public String getMessageType() {
2048                         return messageType;
2049                 }
2050
2051                 /**
2052                  * Gets the callback content.
2053                  */
2054                 public String getContent() {
2055                         return content;
2056                 }
2057         }
2058
2059         /**
2060          * A tool for evaluating XPath expressions.
2061          */
2062         protected class XPathTool {
2063                 private final DocumentBuilderFactory factory;
2064                 private final SimpleNamespaceContext context = new SimpleNamespaceContext();
2065                 private final XPath xpath = XPathFactory.newInstance().newXPath();
2066                 private String xml = null;
2067                 private Document doc = null;
2068
2069                 /**
2070                  * Constructor.
2071                  */
2072                 public XPathTool() {
2073                         factory = DocumentBuilderFactory.newInstance();
2074                         factory.setNamespaceAware(true);
2075                         xpath.setNamespaceContext(context);
2076                 }
2077
2078                 /**
2079                  * Adds a namespace.
2080                  * @param prefix the namespace prefix
2081                  * @param uri the namespace uri
2082                  */
2083                 public synchronized void addNamespace(String prefix, String uri) {
2084                         context.add(prefix, uri);
2085                 }
2086
2087                 /**
2088                  * Sets the XML content to be operated on.
2089                  * @param xml the XML content
2090                  */
2091                 public synchronized void setXML(String xml) {
2092                         this.xml = xml;
2093                         this.doc = null;
2094                 }
2095
2096                 /**
2097                  * Returns the document object.
2098                  * @return the document object, or null if XML has not been set
2099                  * @throws SAXException
2100                  * @throws IOException
2101                  * @throws ParserConfigurationException
2102                  */
2103                 public synchronized Document getDocument()
2104                                 throws ParserConfigurationException, IOException, SAXException {
2105                         if (xml == null) {
2106                                 return null;
2107                         }
2108
2109                         buildDocument();
2110                         return doc;
2111                 }
2112
2113                 /**
2114                  * Evaluates the specified XPath expression and returns a string result.
2115                  * This method throws exceptions on error.
2116                  * @param expression the expression
2117                  * @return the result object
2118                  * @throws ParserConfigurationException
2119                  * @throws IOException
2120                  * @throws SAXException
2121                  * @throws XPathExpressionException on error
2122                  */
2123                 public synchronized String evaluate(String expression)
2124                                 throws ParserConfigurationException, SAXException,
2125                                 IOException, XPathExpressionException {
2126                         return (String) evaluate(expression, XPathConstants.STRING);
2127                 }
2128
2129                 /**
2130                  * Evaluates the specified XPath expression.
2131                  * This method throws exceptions on error.
2132                  * @param expression the expression
2133                  * @param returnType the return type
2134                  * @return the result object
2135                  * @throws ParserConfigurationException
2136                  * @throws IOException
2137                  * @throws SAXException
2138                  * @throws XPathExpressionException on error
2139                  */
2140                 public synchronized Object evaluate(String expression, QName returnType)
2141                                 throws ParserConfigurationException, SAXException,
2142                                 IOException, XPathExpressionException {
2143
2144                         buildDocument();
2145                         XPathExpression expr = xpath.compile(expression);
2146                         return expr.evaluate(doc, returnType);
2147                 }
2148
2149                 /**
2150                  * Private helper method that builds the document object.
2151                  * Assumes the calling method is synchronized.
2152                  * @throws ParserConfigurationException
2153                  * @throws IOException
2154                  * @throws SAXException
2155                  */
2156                 private void buildDocument() throws ParserConfigurationException,
2157                                 IOException, SAXException {
2158                         if (doc == null) {
2159                                 if (xml == null) {
2160                                         throw new IOException("XML input is null");
2161                                 }
2162
2163                                 DocumentBuilder builder = factory.newDocumentBuilder();
2164                                 InputSource source = new InputSource(new StringReader(xml));
2165                                 doc = builder.parse(source);
2166                         }
2167                 }
2168         }
2169
2170         /**
2171          * A NamespaceContext class based on a Map.
2172          */
2173         private class SimpleNamespaceContext implements NamespaceContext {
2174                 private Map<String, String> prefixMap = new HashMap<>();
2175                 private Map<String, String> uriMap = new HashMap<>();
2176
2177                 public synchronized void add(String prefix, String uri) {
2178                         prefixMap.put(prefix, uri);
2179                         uriMap.put(uri, prefix);
2180                 }
2181
2182                 @Override
2183                 public synchronized String getNamespaceURI(String prefix) {
2184                         return prefixMap.get(prefix);
2185                 }
2186
2187                 @Override
2188                 public Iterator<String> getPrefixes(String uri) {
2189                         List<String> list = new ArrayList<>();
2190                         String prefix = uriMap.get(uri);
2191                         if (prefix != null) {
2192                                 list.add(prefix);
2193                         }
2194                         return list.iterator();
2195                 }
2196
2197                 @Override
2198                 public String getPrefix(String uri) {
2199                         return uriMap.get(uri);
2200                 }
2201         }
2202
2203         /**
2204          * A VnfNotify XPathTool.
2205          */
2206         protected class VnfNotifyXPathTool extends XPathTool {
2207                 public VnfNotifyXPathTool() {
2208                         addNamespace("tns", "http://org.onap.so/vnfNotify");
2209                 }
2210         }
2211
2212         /**
2213          * Helper class to make it easier to create this type.
2214          */
2215         private static class CreateVnfNotificationOutputs
2216                         extends CreateVnfNotification.Outputs {
2217                 public void add(String key, String value) {
2218                         Entry entry = new Entry();
2219                         entry.setKey(key);
2220                         entry.setValue(value);
2221                         getEntry().add(entry);
2222                 }
2223         }
2224
2225         /**
2226          * Helper class to make it easier to create this type.
2227          */
2228         private static class UpdateVnfNotificationOutputs
2229                         extends UpdateVnfNotification.Outputs {
2230                 public void add(String key, String value) {
2231                         Entry entry = new Entry();
2232                         entry.setKey(key);
2233                         entry.setValue(value);
2234                         getEntry().add(entry);
2235                 }
2236         }
2237 }