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