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