/*-
* ============LICENSE_START=======================================================
* ONAP - SO
* ================================================================================
* Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
* ================================================================================
* Modifications Copyright (c) 2019 Samsung
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============LICENSE_END=========================================================
*/
package org.onap.so.bpmn.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.onap.so.bpmn.core.json.JsonUtils.getJsonValue;
import static org.onap.so.bpmn.core.json.JsonUtils.updJsonValue;
import java.io.IOException;
import java.io.StringReader;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.ProcessEngineException;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceQuery;
import org.camunda.bpm.engine.test.ProcessEngineRule;
import org.camunda.bpm.engine.variable.impl.VariableMapImpl;
import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.XMLUnit;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Rule;
import org.onap.so.bpmn.common.adapter.sdnc.CallbackHeader;
import org.onap.so.bpmn.common.adapter.sdnc.SDNCAdapterCallbackRequest;
import org.onap.so.bpmn.common.adapter.sdnc.SDNCAdapterResponse;
import org.onap.so.bpmn.common.adapter.vnf.CreateVnfNotification;
import org.onap.so.bpmn.common.adapter.vnf.DeleteVnfNotification;
import org.onap.so.bpmn.common.adapter.vnf.MsoExceptionCategory;
import org.onap.so.bpmn.common.adapter.vnf.MsoRequest;
import org.onap.so.bpmn.common.adapter.vnf.UpdateVnfNotification;
import org.onap.so.bpmn.common.adapter.vnf.VnfRollback;
import org.onap.so.bpmn.common.workflow.context.WorkflowResponse;
import org.onap.so.bpmn.common.workflow.service.SDNCAdapterCallbackServiceImpl;
import org.onap.so.bpmn.common.workflow.service.VnfAdapterNotifyServiceImpl;
import org.onap.so.bpmn.common.workflow.service.WorkflowAsyncResource;
import org.onap.so.bpmn.common.workflow.service.WorkflowMessageResource;
import org.onap.so.bpmn.common.workflow.service.WorkflowResource;
import org.onap.so.bpmn.core.domain.Resource;
import org.onap.so.bpmn.core.domain.ServiceDecomposition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A base class for Workflow tests.
*
* WireMock response transformers may be specified by declaring public static fields with the @WorkflowTestTransformer
* annotation. For example:
*
*
* @WorkflowTestTransformer
* public static final ResponseTransformer sdncAdapterMockTransformer = new SDNCAdapterMockTransformer();
*
*/
public abstract class WorkflowTest {
private static final Logger logger = LoggerFactory.getLogger(WorkflowTest.class);
// TODO this is not used anymore, can maybe be removed
@Rule
public ProcessEngineRule processEngineRule;
@Autowired
protected WorkflowResource workflowResourceSync;
@Autowired
protected ProcessEngine processEngine;
@Autowired
protected RuntimeService runtimeService;
@Autowired
protected HistoryService historyService;
@Autowired
private WorkflowAsyncResource workflowResource;
@Autowired
private WorkflowMessageResource workflowMessageResource;
@Autowired
SDNCAdapterCallbackServiceImpl callbackService;
/**
* Content-Type for XML.
*/
protected static final String XML = "application/xml";
/**
* Content-Type for JSON.
*/
protected static final String JSON = "application/json; charset=UTF-8";
private static final int timeout = 2000;
/**
* Constructor.
*/
public WorkflowTest() throws RuntimeException {}
/**
* The current request ID. Normally set when an "invoke" method is called.
*/
protected volatile String msoRequestId = null;
/**
* The current service instance ID. Normally set when an "invoke" method is called.
*/
protected volatile String msoServiceInstanceId = null;
/**
* Logs a test start method.
*/
protected void logStart() {
logger.debug("STARTED TEST");
}
/**
* Logs a test end method.
*/
protected void logEnd() {
logger.debug("ENDED TEST");
}
/**
* Invokes a subprocess.
*
* @param processKey the process key
* @param businessKey a unique key that will identify the process instance
* @param injectedVariables variables to inject into the process
*/
protected void invokeSubProcess(String processKey, String businessKey, Map injectedVariables) {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List arguments = runtimeMxBean.getInputArguments();
logger.debug("JVM args = {}", arguments);
msoRequestId = (String) injectedVariables.get("mso-request-id");
String requestId = (String) injectedVariables.get("msoRequestId");
if (msoRequestId == null && requestId == null) {
String msg = "mso-request-id variable was not provided";
logger.debug(msg);
fail(msg);
}
// Note: some scenarios don't have a service-instance-id, may be null
msoServiceInstanceId = (String) injectedVariables.get("mso-service-instance-id");
runtimeService.startProcessInstanceByKey(processKey, businessKey, injectedVariables);
}
protected String invokeSubProcess(String processKey, Map injectedVariables) {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List arguments = runtimeMxBean.getInputArguments();
logger.debug("JVM args = {}", arguments);
msoRequestId = (String) injectedVariables.get("mso-request-id");
String requestId = (String) injectedVariables.get("msoRequestId");
if (msoRequestId == null && requestId == null) {
String msg = "mso-request-id variable was not provided";
logger.debug(msg);
fail(msg);
}
// Note: some scenarios don't have a service-instance-id, may be null
msoServiceInstanceId = (String) injectedVariables.get("mso-service-instance-id");
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey(processKey, msoRequestId, injectedVariables);
return processInstance.getId();
}
/**
* Invokes an asynchronous process. Errors are handled with junit assertions and will cause the test to fail.
*
* @param processKey the process key
* @param schemaVersion the API schema version, e.g. "v1"
* @param businessKey a unique key that will identify the process instance
* @param request the request
* @return a TestAsyncResponse object associated with the test
* @throws InterruptedException
*/
protected TestAsyncResponse invokeAsyncProcess(String processKey, String schemaVersion, String businessKey,
String request) throws InterruptedException {
return invokeAsyncProcess(processKey, schemaVersion, businessKey, request, null);
}
/**
* Invokes an asynchronous process. Errors are handled with junit assertions and will cause the test to fail.
*
* @param processKey the process key
* @param schemaVersion the API schema version, e.g. "v1"
* @param businessKey a unique key that will identify the process instance
* @param request the request
* @param injectedVariables optional variables to inject into the process
* @return a TestAsyncResponse object associated with the test
* @throws InterruptedException
*/
protected TestAsyncResponse invokeAsyncProcess(String processKey, String schemaVersion, String businessKey,
String request, Map injectedVariables) {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List arguments = runtimeMxBean.getInputArguments();
logger.debug("JVM args = {}", arguments);
Map variables = createVariables(schemaVersion, businessKey, request, injectedVariables, false);
VariableMapImpl variableMapImpl = createVariableMapImpl(variables);
logger.debug("Sending {} to {} process", request, processKey);
TestAsyncResponse asyncResponse = new TestAsyncResponse();
asyncResponse.setResponse(workflowResource.startProcessInstanceByKey(processKey, variableMapImpl));
return asyncResponse;
}
/**
* Invokes an asynchronous process. Errors are handled with junit assertions and will cause the test to fail.
*
* @param processKey the process key
* @param schemaVersion the API schema version, e.g. "v1"
* @param businessKey a unique key that will identify the process instance
* @param request the request
* @param injectedVariables optional variables to inject into the process
* @param serviceInstantiationModel indicates whether this method is being invoked for a flow that is designed using
* the service instantiation model
* @return a TestAsyncResponse object associated with the test
* @throws InterruptedException
*/
protected Response invokeAsyncProcess(String processKey, String schemaVersion, String businessKey, String request,
Map injectedVariables, boolean serviceInstantiationModel) {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List arguments = runtimeMxBean.getInputArguments();
logger.debug("JVM args = {}", arguments);
Map variables =
createVariables(schemaVersion, businessKey, request, injectedVariables, serviceInstantiationModel);
VariableMapImpl variableMapImpl = createVariableMapImpl(variables);
logger.debug("Sending {} to {} process", request, processKey);
return workflowResource.startProcessInstanceByKey(processKey, variableMapImpl);
}
/**
* Private helper method that creates a variable map for a request. Errors are handled with junit assertions and
* will cause the test to fail.
*
* @param schemaVersion the API schema version, e.g. "v1"
* @param businessKey a unique key that will identify the process instance
* @param request the request
* @param injectedVariables optional variables to inject into the process
* @param serviceInstantiationModel indicates whether this method is being invoked for a flow that is designed using
* the service instantiation model
* @return a variable map
*/
private Map createVariables(String schemaVersion, String businessKey, String request,
Map injectedVariables, boolean serviceInstantiationModel) {
Map variables = new HashMap<>();
// These variables may be overridded by injected variables.
variables.put("mso-service-request-timeout", "180");
variables.put("isDebugLogEnabled", "true");
// These variables may not be overridded by injected variables.
String[] notAllowed = new String[] {"mso-schema-version", "mso-business-key", "bpmnRequest", "mso-request-id",
"mso-service-instance-id"};
if (injectedVariables != null) {
for (String key : injectedVariables.keySet()) {
for (String var : notAllowed) {
if (var.equals(key)) {
String msg = "Cannot specify " + var + " in injected variables";
logger.debug(msg);
fail(msg);
}
}
variables.put(key, injectedVariables.get(key));
}
}
variables.put("mso-schema-version", schemaVersion);
variables.put("mso-business-key", businessKey);
variables.put("bpmnRequest", request);
if (serviceInstantiationModel) {
/*
* The request ID and the service instance ID are generated for flows that follow the service instantiation
* model unless "requestId" and "serviceInstanceId" are injected variables.
*/
try {
if (injectedVariables != null) {
msoRequestId = (String) injectedVariables.get("requestId");
variables.put("mso-request-id", msoRequestId);
msoServiceInstanceId = (String) injectedVariables.get("serviceInstanceId");
variables.put("mso-service-instance-id", msoServiceInstanceId);
}
} catch (Exception e) {
}
if (msoRequestId == null || msoRequestId.trim().equals("")) {
logger.debug("No requestId element in injectedVariables");
variables.put("mso-request-id", UUID.randomUUID().toString());
}
if (msoServiceInstanceId == null || msoServiceInstanceId.trim().equals("")) {
logger.debug("No seviceInstanceId element in injectedVariables");
variables.put("mso-service-instance-id", UUID.randomUUID().toString());
}
} else {
msoRequestId = getXMLTextElement(request, "request-id");
if (msoRequestId == null) {
// check in injected variables
try {
msoRequestId = (String) injectedVariables.get("requestId");
} catch (Exception e) {
}
if (msoRequestId == null || msoRequestId.trim().equals("")) {
String msg = "No request-id element in " + request;
logger.debug(msg);
fail(msg);
}
}
variables.put("mso-request-id", msoRequestId);
// Note: some request types don't have a service-instance-id
msoServiceInstanceId = getXMLTextElement(request, "service-instance-id");
if (msoServiceInstanceId != null) {
variables.put("mso-service-instance-id", msoServiceInstanceId);
}
}
return variables;
}
/**
* Private helper method that creates a camunda VariableMapImpl from a simple variable map.
*
* @param variables the simple variable map
* @return a VariableMap
*/
private VariableMapImpl createVariableMapImpl(Map variables) {
Map wrappedVariables = new HashMap<>();
for (String key : variables.keySet()) {
Object value = variables.get(key);
wrappedVariables.put(key, wrapVariableValue(value));
}
VariableMapImpl variableMapImpl = new VariableMapImpl();
variableMapImpl.put("variables", wrappedVariables);
return variableMapImpl;
}
/**
* Private helper method that wraps a variable value for inclusion in a camunda VariableMapImpl.
*
* @param value the variable value
* @return the wrapped variable
*/
private Map wrapVariableValue(Object value) {
HashMap valueMap = new HashMap<>();
valueMap.put("value", value);
return valueMap;
}
/**
* Receives a response from an asynchronous process. Errors are handled with junit assertions and will cause the
* test to fail.
*
* @param businessKey the process business key
* @param asyncResponse the TestAsyncResponse object associated with the test
* @param timeout the timeout in milliseconds
* @return the WorkflowResponse
*/
protected WorkflowResponse receiveResponse(String businessKey, TestAsyncResponse asyncResponse, long timeout) {
logger.debug("Waiting {}ms for process with business key {} to send a response", timeout, businessKey);
long now = System.currentTimeMillis() + timeout;
long endTime = now + timeout;
while (now <= endTime) {
Response response = asyncResponse.getResponse();
if (response != null) {
logger.debug("Received a response from process with business key {}", businessKey);
Object entity = response.getEntity();
if (!(entity instanceof WorkflowResponse)) {
String msg = "Response entity is " + (entity == null ? "null" : entity.getClass().getName())
+ ", expected WorkflowResponse";
logger.debug(msg);
fail(msg);
return null; // unreachable
}
return (WorkflowResponse) entity;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
String msg = "Interrupted waiting for a response from process with business key " + businessKey;
logger.debug(msg);
fail(msg);
return null; // unreachable
}
now = System.currentTimeMillis();
}
String msg = "No response received from process with business key " + businessKey + " within " + timeout + "ms";
logger.debug(msg);
fail("Process with business key " + businessKey + " did not end within 10000ms");
return null; // unreachable
}
/**
* Runs a program to inject SDNC callback data into the test environment. A program is essentially just a list of
* keys that identify callback data to be injected, in sequence. An example program:
*
*
* reserve, assign, delete:ERR
*
*
* Errors are handled with junit assertions and will cause the test to fail.
*
* @param callbacks an object containing callback data for the program
* @param program the program to execute
*/
protected void injectSDNCRestCallbacks(CallbackSet callbacks, String program) {
String[] cmds = program.replaceAll("\\s+", "").split(",");
for (String cmd : cmds) {
String action = cmd;
String modifier = "STD";
if (cmd.contains(":")) {
String[] parts = cmd.split(":");
action = parts[0];
modifier = parts[1];
}
String content = null;
String contentType = null;
if ("STD".equals(modifier)) {
CallbackData callbackData = callbacks.get(action);
if (callbackData == null) {
String msg = "No callback defined for '" + action + "' SDNC request";
logger.debug(msg);
fail(msg);
}
content = callbackData.getContent();
contentType = callbackData.getContentType();
} else if ("ERR".equals(modifier)) {
content =
"{\"SDNCServiceError\":{\"sdncRequestId\":\"((REQUEST-ID))\",\"responseCode\":\"500\",\"responseMessage\":\"SIMULATED ERROR FROM SDNC ADAPTER\",\"ackFinalIndicator\":\"Y\"}}";
contentType = JSON;
} else {
String msg = "Invalid SDNC program modifier: '" + modifier + "'";
logger.debug(msg);
fail(msg);
}
if (contentType == null) {
// Default for backward compatibility with existing tests.
contentType = JSON;
}
if (!injectSDNCRestCallback(contentType, content, 10000)) {
fail("Failed to inject SDNC '" + action + "' callback");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Interrupted after injection of SDNC '" + action + "' callback");
}
}
}
/**
* Runs a program to inject SDNC events into the test environment. A program is essentially just a list of keys that
* identify event data to be injected, in sequence. An example program:
*
*
* event1, event2
*
*
* NOTE: Each callback must have a message type associated with it, e.g. "SDNCAEvent". Errors are handled with junit
* assertions and will cause the test to fail.
*
* @param callbacks an object containing event data for the program
* @param program the program to execute
*/
protected void injectSDNCEvents(CallbackSet callbacks, String program) {
injectWorkflowMessages(callbacks, program);
}
/**
* Runs a program to inject SDNC callback data into the test environment. A program is essentially just a list of
* keys that identify callback data to be injected, in sequence. An example program:
*
*
* reserve, assign, delete:ERR
*
*
* Errors are handled with junit assertions and will cause the test to fail. Uses the static/default timeout value
* for backward compatibility.
*
* @param callbacks an object containing callback data for the program
* @param program the program to execute
*/
protected void injectSDNCCallbacks(CallbackSet callbacks, String program) {
injectSDNCCallbacks(callbacks, program, timeout);
}
/**
* Runs a program to inject SDNC callback data into the test environment. A program is essentially just a list of
* keys that identify callback data to be injected, in sequence. An example program:
*
*
* reserve, assign, delete:ERR
*
*
* Errors are handled with junit assertions and will cause the test to fail.
*
* @param callbacks an object containing callback data for the program
* @param program the program to execute
* @param timeout a timeout value to wait for the callback
*/
protected void injectSDNCCallbacks(CallbackSet callbacks, String program, int timeout) {
String[] cmds = program.replaceAll("\\s+", "").split(",");
for (String cmd : cmds) {
String action = cmd;
String modifier = "STD";
if (cmd.contains(":")) {
String[] parts = cmd.split(":");
action = parts[0];
modifier = parts[1];
}
String content = null;
int respCode = 200;
String respMsg = "OK";
if ("STD".equals(modifier)) {
CallbackData callbackData = callbacks.get(action);
if (callbackData == null) {
String msg = "No callback defined for '" + action + "' SDNC request";
logger.debug(msg);
fail(msg);
}
content = callbackData.getContent();
respCode = 200;
respMsg = "OK";
} else if ("CREATED".equals(modifier)) {
CallbackData callbackData = callbacks.get(action);
if (callbackData == null) {
String msg = "No callback defined for '" + action + "' SDNC request";
logger.debug(msg);
fail(msg);
}
content = callbackData.getContent();
respCode = 201;
respMsg = "Created";
} else if ("ERR".equals(modifier)) {
content =
"((REQUEST-ID))500SIMULATED ERROR FROM SDNC ADAPTER";
respCode = 500;
respMsg = "SERVER ERROR";
} else {
String msg = "Invalid SDNC program modifier: '" + modifier + "'";
logger.debug(msg);
fail(msg);
}
if (!injectSDNCCallback(respCode, respMsg, content, 10000)) {
fail("Failed to inject SDNC '" + action + "' callback");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Interrupted after injection of SDNC '" + action + "' callback");
}
}
}
/**
* Runs a program to inject VNF adapter REST callback data into the test environment. A program is essentially just
* a list of keys that identify callback data to be injected, in sequence. An example program:
*
*
* create, rollback
*
*
* Errors are handled with junit assertions and will cause the test to fail.
*
* @param callbacks an object containing callback data for the program
* @param program the program to execute
*/
protected void injectVNFRestCallbacks(CallbackSet callbacks, String program) {
String[] cmds = program.replaceAll("\\s+", "").split(",");
for (String cmd : cmds) {
String action = cmd;
String modifier = "STD";
if (cmd.contains(":")) {
String[] parts = cmd.split(":");
action = parts[0];
modifier = parts[1];
}
String content = null;
String contentType = null;
if ("STD".equals(modifier)) {
CallbackData callbackData = callbacks.get(action);
if (callbackData == null) {
String msg = "No callback defined for '" + action + "' VNF REST request";
logger.debug(msg);
fail(msg);
}
content = callbackData.getContent();
contentType = callbackData.getContentType();
} else if ("ERR".equals(modifier)) {
content = "SIMULATED ERROR FROM VNF ADAPTER";
contentType = "text/plain";
} else {
String msg = "Invalid VNF REST program modifier: '" + modifier + "'";
logger.debug(msg);
fail(msg);
}
if (contentType == null) {
// Default for backward compatibility with existing tests.
contentType = XML;
}
if (!injectVnfAdapterRestCallback(contentType, content, 10000)) {
fail("Failed to inject VNF REST '" + action + "' callback");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Interrupted after injection of VNF REST '" + action + "' callback");
}
}
}
/**
* Waits for the number of running processes with the specified process definition key to equal a particular count.
*
* @param processKey the process definition key
* @param count the desired count
* @param timeout the timeout in milliseconds
*/
protected void waitForRunningProcessCount(String processKey, int count, long timeout) {
logger.debug("Waiting {}ms for there to be {} {} instances", timeout, count, processKey);
long now = System.currentTimeMillis() + timeout;
long endTime = now + timeout;
int last = -1;
while (now <= endTime) {
int actual = runtimeService.createProcessInstanceQuery().processDefinitionKey(processKey).list().size();
if (actual != last) {
logger.debug("There are now {} {} instances", actual, processKey);
last = actual;
}
if (actual == count) {
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
String msg = "Interrupted waiting for there to be " + count + " " + processKey + " instances";
logger.debug(msg);
fail(msg);
}
now = System.currentTimeMillis();
}
String msg = "Timed out waiting for there to be " + count + " " + processKey + " instances";
logger.debug(msg);
fail(msg);
}
/**
* Waits for the specified process variable to be set.
*
* @param processKey the process definition key
* @param variable the variable name
* @param timeout the timeout in milliseconds
* @return the variable value, or null if it cannot be obtained in the specified time
*/
protected Object getProcessVariable(String processKey, String variable, long timeout) {
logger.debug("Waiting " + timeout + "ms for " + processKey + "." + variable + " to be set");
long now = System.currentTimeMillis() + timeout;
long endTime = now + timeout;
ProcessInstance processInstance = null;
Object value = null;
while (value == null) {
if (now > endTime) {
if (processInstance == null) {
logger.debug("Timed out waiting for " + processKey + " to start");
} else {
logger.debug("Timed out waiting for " + processKey + "[" + processInstance.getId() + "]." + variable
+ " to be set");
}
return null;
}
ProcessInstanceQuery processInstanceQuery = null;
if (processInstance == null) {
processInstanceQuery = runtimeService.createProcessInstanceQuery().processDefinitionKey(processKey);
}
if (processInstanceQuery.count() == 1 || processInstanceQuery.count() == 0) {
processInstance = processInstanceQuery.singleResult();
} else {
// TODO There shouldnt be more than one in the list but seems to be happening, need to figure out why
// happening and best way to get correct one from list
logger.debug("Process Instance Query returned {} instance. Getting the last instance in the list",
processInstanceQuery.count());
List processList = processInstanceQuery.list();
processInstance = processList.get((processList.size() - 1));
}
if (processInstance != null) {
value = runtimeService.getVariable(processInstance.getId(), variable);
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
logger.debug("Interrupted waiting for {}.{} to be set", processKey, variable);
return null;
}
now = System.currentTimeMillis();
}
logger.debug(processKey + "[" + processInstance.getId() + "]." + variable + "=" + value);
return value;
}
/**
* Injects a single SDNC adapter callback request. The specified callback data may contain the placeholder string
* ((REQUEST-ID)) which is replaced with the actual SDNC request ID. Note: this is not the requestId in the original
* MSO request.
*
* @param contentType the HTTP content type for the callback
* @param content the content of the callback
* @param timeout the timeout in milliseconds
* @return true if the callback could be injected, false otherwise
*/
protected boolean injectSDNCRestCallback(String contentType, String content, long timeout) {
String sdncRequestId = (String) getProcessVariable("SDNCAdapterRestV1", "SDNCAResponse_CORRELATOR", timeout);
if (sdncRequestId == null) {
sdncRequestId = (String) getProcessVariable("SDNCAdapterRestV2", "SDNCAResponse_CORRELATOR", timeout);
}
if (sdncRequestId == null) {
return false;
}
content = content.replace("((REQUEST-ID))", sdncRequestId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{REQUEST-ID}}", sdncRequestId);
logger.debug("Injecting SDNC adapter callback");
Response response = workflowMessageResource.deliver(contentType, "SDNCAResponse", sdncRequestId, content);
logger.debug("Workflow response to SDNC adapter callback: " + response);
return true;
}
/**
* Injects a single SDNC adapter callback request. The specified callback data may contain the placeholder string
* ((REQUEST-ID)) which is replaced with the actual SDNC request ID. Note: this is not the requestId in the original
* MSO request.
*
* @param content the content of the callback
* @param respCode the response code (normally 200)
* @param respMsg the response message (normally "OK")
* @param timeout the timeout in milliseconds
* @return true if the callback could be injected, false otherwise
*/
protected boolean injectSDNCCallback(int respCode, String respMsg, String content, long timeout) {
String sdncRequestId = (String) getProcessVariable("sdncAdapter", "SDNCA_requestId", timeout);
if (sdncRequestId == null) {
return false;
}
content = content.replace("((REQUEST-ID))", sdncRequestId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{REQUEST-ID}}", sdncRequestId);
// TODO this needs to be fixed. It is causing double tags and content
// Need to parse content before setting below since content includes not just RequestData or modify callback
// files to only contain RequestData contents.
logger.debug("Injecting SDNC adapter callback");
CallbackHeader callbackHeader = new CallbackHeader();
callbackHeader.setRequestId(sdncRequestId);
callbackHeader.setResponseCode(String.valueOf(respCode));
callbackHeader.setResponseMessage(respMsg);
SDNCAdapterCallbackRequest sdncAdapterCallbackRequest = new SDNCAdapterCallbackRequest();
sdncAdapterCallbackRequest.setCallbackHeader(callbackHeader);
sdncAdapterCallbackRequest.setRequestData(content);
SDNCAdapterResponse sdncAdapterResponse = callbackService.sdncAdapterCallback(sdncAdapterCallbackRequest);
logger.debug("Workflow response to SDNC adapter callback: " + sdncAdapterResponse);
return true;
}
/**
* Injects a single VNF adapter callback request. The specified callback data may contain the placeholder string
* ((MESSAGE-ID)) which is replaced with the actual message ID. Note: this is not the requestId in the original MSO
* request.
*
* @param contentType the HTTP content type for the callback
* @param content the content of the callback
* @param timeout the timeout in milliseconds
* @return true if the callback could be injected, false otherwise
*/
protected boolean injectVnfAdapterRestCallback(String contentType, String content, long timeout) {
String messageId = (String) getProcessVariable("vnfAdapterRestV1", "VNFAResponse_CORRELATOR", timeout);
if (messageId == null) {
return false;
}
content = content.replace("((MESSAGE-ID))", messageId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{MESSAGE-ID}}", messageId);
logger.debug("Injecting VNF adapter callback");
Response response = workflowMessageResource.deliver(contentType, "VNFAResponse", messageId, content);
logger.debug("Workflow response to VNF adapter callback: {}", response);
return true;
}
/**
* Injects a Create VNF adapter callback request. The specified callback data may contain the placeholder string
* ((MESSAGE-ID)) which is replaced with the actual message ID. It may also contain the placeholder string
* ((REQUEST-ID)) which is replaced request ID of the original MSO request.
*
* @param content the content of the callback
* @param timeout the timeout in milliseconds
* @return true if the callback could be injected, false otherwise
* @throws JAXBException if the content does not adhere to the schema
*/
protected boolean injectCreateVNFCallback(String content, long timeout) {
String messageId = (String) getProcessVariable("vnfAdapterCreateV1", "VNFC_messageId", timeout);
if (messageId == null) {
return false;
}
content = content.replace("((MESSAGE-ID))", messageId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{MESSAGE-ID}}", messageId);
if (content.contains("((REQUEST-ID))")) {
content = content.replace("((REQUEST-ID))", msoRequestId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{REQUEST-ID}}", msoRequestId);
}
logger.debug("Injecting VNF adapter callback");
// Is it possible to unmarshal this with JAXB? I couldn't.
CreateVnfNotification createVnfNotification = new CreateVnfNotification();
XPathTool xpathTool = new VnfNotifyXPathTool();
xpathTool.setXML(content);
try {
String completed = xpathTool.evaluate("/tns:createVnfNotification/tns:completed/text()");
createVnfNotification.setCompleted("true".equals(completed));
String vnfId = xpathTool.evaluate("/tns:createVnfNotification/tns:vnfId/text()");
createVnfNotification.setVnfId(vnfId);
NodeList entries = (NodeList) xpathTool.evaluate("/tns:createVnfNotification/tns:outputs/tns:entry",
XPathConstants.NODESET);
CreateVnfNotificationOutputs outputs = new CreateVnfNotificationOutputs();
for (int i = 0; i < entries.getLength(); i++) {
Node node = entries.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element entry = (Element) node;
String key = entry.getElementsByTagNameNS("*", "key").item(0).getTextContent();
String value = entry.getElementsByTagNameNS("*", "value").item(0).getTextContent();
outputs.add(key, value);
}
}
createVnfNotification.setOutputs(outputs);
VnfRollback rollback = new VnfRollback();
String cloudSiteId = xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:cloudSiteId/text()");
rollback.setCloudSiteId(cloudSiteId);
String cloudOwner = xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:cloudOwner/text()");
rollback.setCloudOwner(cloudOwner);
String requestId =
xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:msoRequest/tns:requestId/text()");
String serviceInstanceId = xpathTool
.evaluate("/tns:createVnfNotification/tns:rollback/tns:msoRequest/tns:serviceInstanceId/text()");
if (requestId != null || serviceInstanceId != null) {
MsoRequest msoRequest = new MsoRequest();
msoRequest.setRequestId(requestId);
msoRequest.setServiceInstanceId(serviceInstanceId);
rollback.setMsoRequest(msoRequest);
}
String tenantCreated =
xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:tenantCreated/text()");
rollback.setTenantCreated("true".equals(tenantCreated));
String tenantId = xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:tenantId/text()");
rollback.setTenantId(tenantId);
String vnfCreated = xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:vnfCreated/text()");
rollback.setVnfCreated("true".equals(vnfCreated));
String rollbackVnfId = xpathTool.evaluate("/tns:createVnfNotification/tns:rollback/tns:vnfId/text()");
rollback.setVnfId(rollbackVnfId);
createVnfNotification.setRollback(rollback);
} catch (Exception e) {
logger.debug("Failed to unmarshal VNF callback content:");
logger.debug(content);
return false;
}
VnfAdapterNotifyServiceImpl notifyService = new VnfAdapterNotifyServiceImpl();
notifyService.createVnfNotification(messageId, createVnfNotification.isCompleted(),
createVnfNotification.getException(), createVnfNotification.getErrorMessage(),
createVnfNotification.getVnfId(), createVnfNotification.getOutputs(),
createVnfNotification.getRollback());
return true;
}
/**
* Injects a Delete VNF adapter callback request. The specified callback data may contain the placeholder string
* ((MESSAGE-ID)) which is replaced with the actual message ID. It may also contain the placeholder string
* ((REQUEST-ID)) which is replaced request ID of the original MSO request.
*
* @param content the content of the callback
* @param timeout the timeout in milliseconds
* @return true if the callback could be injected, false otherwise
* @throws JAXBException if the content does not adhere to the schema
*/
protected boolean injectDeleteVNFCallback(String content, long timeout) {
String messageId = (String) getProcessVariable("vnfAdapterDeleteV1", "VNFDEL_uuid", timeout);
if (messageId == null) {
return false;
}
content = content.replace("((MESSAGE-ID))", messageId);
// Deprecated usage. All test code should switch to the (( ... )) syntax.
content = content.replace("{{MESSAGE-ID}}", messageId);
logger.debug("Injecting VNF adapter delete callback");
// Is it possible to unmarshal this with JAXB? I couldn't.
DeleteVnfNotification deleteVnfNotification = new DeleteVnfNotification();
XPathTool xpathTool = new VnfNotifyXPathTool();
xpathTool.setXML(content);
try {
String completed = xpathTool.evaluate("/tns:deleteVnfNotification/tns:completed/text()");
deleteVnfNotification.setCompleted("true".equals(completed));
// if notification failure, set the exception and error message
if (deleteVnfNotification.isCompleted() == false) {
deleteVnfNotification.setException(MsoExceptionCategory.INTERNAL);
deleteVnfNotification
.setErrorMessage(xpathTool.evaluate("/tns:deleteVnfNotification/tns:errorMessage/text()"));
}
} catch (Exception e) {
logger.debug("Failed to unmarshal VNF Delete callback content:");
logger.debug(content);
return false;
}
VnfAdapterNotifyServiceImpl notifyService = new VnfAdapterNotifyServiceImpl();
notifyService.deleteVnfNotification(messageId, deleteVnfNotification.isCompleted(),
deleteVnfNotification.getException(), deleteVnfNotification.getErrorMessage());
return true;
}
/**
* Runs a program to inject workflow messages into the test environment. A program is essentially just a list of
* keys that identify event data to be injected, in sequence. An example program:
*
*
* event1, event2
*
*
* Errors are handled with junit assertions and will cause the test to fail. NOTE: Each callback must have a
* workflow message type associated with it.
*
* @param callbacks an object containing event data for the program
* @param program the program to execute
*/
protected void injectWorkflowMessages(CallbackSet callbacks, String program) {
String[] cmds = program.replaceAll("\\s+", "").split(",");
for (String cmd : cmds) {
String action = cmd;
String modifier = "STD";
if (cmd.contains(":")) {
String[] parts = cmd.split(":");
action = parts[0];
modifier = parts[1];
}
String messageType = null;
String content = null;
String contentType = null;
if ("STD".equals(modifier)) {
CallbackData callbackData = callbacks.get(action);
if (callbackData == null) {
String msg = "No '" + action + "' workflow message callback is defined";
logger.debug(msg);
fail(msg);
}
messageType = callbackData.getMessageType();
if (messageType == null || messageType.trim().equals("")) {
String msg = "No workflow message type is defined in the '" + action + "' callback";
logger.debug(msg);
fail(msg);
}
content = callbackData.getContent();
contentType = callbackData.getContentType();
} else {
String msg = "Invalid workflow message program modifier: '" + modifier + "'";
logger.debug(msg);
fail(msg);
}
if (!injectWorkflowMessage(contentType, messageType, content, 10000)) {
fail("Failed to inject '" + action + "' workflow message");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Interrupted after injection of '" + action + "' workflow message");
}
}
}
/**
* Injects a workflow message. The specified callback data may contain the placeholder string ((CORRELATOR)) which
* is replaced with the actual correlator value.
*
* @param contentType the HTTP contentType for the message (possibly null)
* @param messageType the message type
* @param content the message content (possibly null)
* @param timeout the timeout in milliseconds
* @return true if the message could be injected, false otherwise
*/
protected boolean injectWorkflowMessage(String contentType, String messageType, String content, long timeout) {
String correlator = (String) getProcessVariable("ReceiveWorkflowMessage", messageType + "_CORRELATOR", timeout);
if (correlator == null) {
return false;
}
if (content != null) {
content = content.replace("((CORRELATOR))", correlator);
}
logger.debug("Injecting " + messageType + " message");
Response response = workflowMessageResource.deliver(contentType, messageType, correlator, content);
logger.debug("Workflow response to {} message: {}", messageType, response);
return true;
}
/**
* Wait for the process to end.
*
* @param businessKey the process business key
* @param timeout the amount of time to wait, in milliseconds
*/
protected void waitForProcessEnd(String businessKey, long timeout) {
logger.debug("Waiting {}ms for process with business key {} to end", timeout, businessKey);
long now = System.currentTimeMillis() + timeout;
long endTime = now + timeout;
while (now <= endTime) {
if (isProcessEnded(businessKey)) {
logger.debug("Process with business key {} has ended", businessKey);
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
String msg = "Interrupted waiting for process with business key " + businessKey + " to end";
logger.debug(msg);
fail(msg);
}
now = System.currentTimeMillis();
}
String msg = "Process with business key " + businessKey + " did not end within " + timeout + "ms";
logger.debug(msg);
fail(msg);
}
/**
* Wait for the process to end. Must be used when multiple process instances exist with this same business key such
* as when its passed to subflows or shared across multiple processes.
*
* @param businessKey the process business key
* @param processName the process definition name
* @param timeout the amount of time to wait, in milliseconds
* @author cb645j
*/
protected void waitForProcessEnd(String businessKey, String processName, long timeout) {
logger.debug("Waiting {}ms for process with business key {} to end", timeout, businessKey);
long now = System.currentTimeMillis() + timeout;
long endTime = now + timeout;
while (now <= endTime) {
if (isProcessEnded(businessKey, processName)) {
logger.debug("Process with business key {} has ended", businessKey);
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
String msg = "Interrupted waiting for process with business key " + businessKey + " to end";
logger.debug(msg);
fail(msg);
}
now = System.currentTimeMillis();
}
String msg = "Process with business key " + businessKey + " did not end within " + timeout + "ms";
logger.debug(msg);
fail(msg);
}
/**
* Verifies that the specified historic process variable has the specified value. If the variable does not have the
* specified value, the test is failed.
*
* @param businessKey the process business key
* @param variable the variable name
* @param value the expected variable value
*/
protected void checkVariable(String businessKey, String variable, Object value) {
if (!isProcessEnded(businessKey)) {
fail("Cannot get historic variable " + variable + " because process with business key " + businessKey
+ " has not ended");
}
Object variableValue = getVariableFromHistory(businessKey, variable);
assertEquals(value, variableValue);
}
/**
* Checks to see if the specified process is ended.
*
* @param businessKey the process business Key
* @return true if the process is ended
*/
protected boolean isProcessEnded(String businessKey) {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).singleResult();
return processInstance != null && processInstance.getEndTime() != null;
}
/**
* Checks to see if the specified process is ended.
*
* @param processInstanceId the process Instance Id
* @return true if the process is ended
*/
protected boolean isProcessEndedByProcessInstanceId(String processInstanceId) {
HistoricProcessInstance processInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
return processInstance != null && processInstance.getEndTime() != null;
}
/**
* Checks to see if the specified process is ended.
*
* @author cb645j
*/
// TODO combine into 1
private boolean isProcessEnded(String businessKey, String processName) {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).processDefinitionName(processName).singleResult();
return processInstance != null && processInstance.getEndTime() != null;
}
/**
* Gets a variable value from a historical process instance. The business key must be unique.
*
* @param businessKey the process business key
* @param variableName the variable name
* @return the variable value or null if the variable does not exist
*/
protected Object getVariableFromHistory(String businessKey, String variableName) {
try {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).singleResult();
if (processInstance == null) {
return null;
}
HistoricVariableInstance v = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
return v == null ? null : v.getValue();
} catch (Exception e) {
logger.debug("Error retrieving variable {} from historical process with business key {}: ", variableName,
businessKey, e);
return null;
}
}
protected Object getVariableFromHistoryByProcessInstanceId(String processInstanceId, String variableName) {
try {
HistoricVariableInstance v = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId).variableName(variableName).singleResult();
return v == null ? null : v.getValue();
} catch (Exception e) {
logger.debug("Error retrieving variable {} from historical process with processInstanceId {}: ",
variableName, processInstanceId, e);
return null;
}
}
/**
* Gets a variable value from a process instance based on businessKey and process name. Must be used when multiple
* instances exist with the same business key such as when business key is passed to subflows or shared across
* multiple processes. This method can obtain variables from mainflows and from subflows.
*
* @param businessKey the process business key
* @param processName the process definition name
* @param variableName the variable name
* @return the variable value or null if the variable does not exist
* @author cb645j
*/
protected Object getVariableFromHistory(String businessKey, String processName, String variableName) {
try {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).processDefinitionName(processName).singleResult();
if (processInstance == null) {
return null;
}
HistoricVariableInstance variable = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
return variable == null ? null : variable.getValue();
} catch (ProcessEngineException e) {
logger.debug(
"Multiple proccess instances exist with process name {} and business key {}. Must pass instance "
+ "index as a parameter.",
processName, businessKey);
return null;
} catch (Exception e) {
logger.debug("Error retrieving variable {} from historical process for process {} with business key {}: ",
variableName, processName, businessKey, e);
return null;
}
}
/**
* Gets the value of a process variable from x instance of y process. Must be used when multiple instances exist
* with the same business key AND process name. This method shall be used primarily for obtaining subflow variables
* when the business key is passed to the subflow AND the subflow is called multiple times in a given flow.
*
* @param businessKey the process business key
* @param processName the name of the subflow that contains the variable
* @param variableName the variable name
* @param processInstanceIndex the instance in which the subprocess was called
* @return the variable value or null if the variable does not exist
* @author cb645j
*/
protected Object getVariableFromHistory(String businessKey, int subflowInstanceIndex, String processName,
String variableName) {
try {
List processInstanceList = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).processDefinitionName(processName).list();
if (processInstanceList == null) {
return null;
}
processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
HistoricProcessInstance processInstance = processInstanceList.get(subflowInstanceIndex);
HistoricVariableInstance variable = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
return variable == null ? null : variable.getValue();
} catch (Exception e) {
logger.debug("Error retrieving variable {} from historical process for process {} with business key {}: ",
variableName, processName, businessKey, e);
return null;
}
}
/**
* Gets the value of a subflow variable from the specified subflow's historical process instance.
*
* DEPRECATED - Use method getVariableFromHistory(businessKey, processName, variableName) instead
*
* @param subflowName - the name of the subflow that contains the variable
* @param variableName the variable name
*
* @return the variable value, or null if the variable could not be obtained
*
*/
@Deprecated
protected Object getVariableFromSubflowHistory(String subflowName, String variableName) {
try {
List processInstanceList =
historyService.createHistoricProcessInstanceQuery().processDefinitionName(subflowName).list();
if (processInstanceList == null) {
return null;
}
processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
HistoricProcessInstance processInstance = processInstanceList.get(0);
HistoricVariableInstance v = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
return v == null ? null : v.getValue();
} catch (Exception e) {
logger.debug("Error retrieving variable {} from sub flow: {}, Exception is: ", variableName, subflowName,
e);
return null;
}
}
/**
* Gets the value of a subflow variable from the subflow's historical process x instance.
*
* DEPRECATED: Use method getVariableFromHistory(businessKey, processInstanceIndex, processName, variableName)
* instead
*
* @param subflowName - the name of the subflow that contains the variable
* @param variableName the variable name
* @param subflowInstanceIndex - the instance of the subflow (use when same subflow is called more than once from
* mainflow)
*
* @return the variable value, or null if the variable could not be obtained
*/
@Deprecated
protected Object getVariableFromSubflowHistory(int subflowInstanceIndex, String subflowName, String variableName) {
try {
List processInstanceList =
historyService.createHistoricProcessInstanceQuery().processDefinitionName(subflowName).list();
if (processInstanceList == null) {
return null;
}
processInstanceList.sort((m1, m2) -> m1.getStartTime().compareTo(m2.getStartTime()));
HistoricProcessInstance processInstance = processInstanceList.get(subflowInstanceIndex);
HistoricVariableInstance v = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId()).variableName(variableName).singleResult();
return v == null ? null : v.getValue();
} catch (Exception e) {
logger.debug("Error retrieving variable {} from {} instance index of sub flow: {}, Exception is: ",
variableName, subflowInstanceIndex, subflowName, e);
return null;
}
}
/**
* Extracts text from an XML element. This method is not namespace aware (namespaces are ignored). The first
* matching element is selected.
*
* @param xml the XML document or fragment
* @param tag the desired element, e.g. ""
* @return the element text, or null if the element was not found
*/
protected String getXMLTextElement(String xml, String tag) {
xml = removeXMLNamespaces(xml);
if (!tag.startsWith("<")) {
tag = "<" + tag + ">";
}
int start = xml.indexOf(tag);
if (start == -1) {
return null;
}
int end = xml.indexOf('<', start + tag.length());
if (end == -1) {
return null;
}
return xml.substring(start + tag.length(), end);
}
/**
* Removes namespace definitions and prefixes from XML, if any.
*/
private String removeXMLNamespaces(String xml) {
// remove xmlns declaration
xml = xml.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
// remove opening tag prefix
xml = xml.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
// remove closing tags prefix
xml = xml.replaceAll("()(\\w+:)(.*?>)", "$1$3");
// remove extra spaces left when xmlns declarations are removed
xml = xml.replaceAll("\\s+>", ">");
return xml;
}
/**
* Asserts that two XML documents are semantically equivalent. Differences in whitespace or in namespace usage do
* not affect the comparison.
*
* @param expected the expected XML
* @param actual the XML to test
* @throws SAXException
* @throws IOException
*/
public static void assertXMLEquals(String expected, String actual) throws SAXException, IOException {
XMLUnit.setIgnoreWhitespace(true);
XMLUnit.setIgnoreAttributeOrder(true);
DetailedDiff diff = new DetailedDiff(XMLUnit.compareXML(expected, actual));
List> allDifferences = diff.getAllDifferences();
assertEquals("Differences found: " + diff.toString(), 0, allDifferences.size());
}
/**
* A test implementation of AsynchronousResponse.
*/
public class TestAsyncResponse {
Response response = null;
/**
* {@inheritDoc}
*/
public synchronized void setResponse(Response response) {
this.response = response;
}
/**
* Gets the response.
*
* @return the response, or null if none has been produced yet
*/
public synchronized Response getResponse() {
return response;
}
}
/**
* An object that contains callback data for a "program".
*/
public class CallbackSet {
private final Map map = new HashMap<>();
/**
* Add untyped callback data to the set.
*
* @param action the action with which the data is associated
* @param content the callback data
*/
public void put(String action, String content) {
map.put(action, new CallbackData(null, null, content));
}
/**
* Add callback data to the set.
*
* @param action the action with which the data is associated
* @param messageType the callback message type
* @param content the callback data
*/
public void put(String action, String messageType, String content) {
map.put(action, new CallbackData(null, messageType, content));
}
/**
* Add callback data to the set.
*
* @param action the action with which the data is associated
* @param contentType the callback HTTP content type
* @param messageType the callback message type
* @param content the callback data
*/
public void put(String action, String contentType, String messageType, String content) {
map.put(action, new CallbackData(contentType, messageType, content));
}
/**
* Retrieve callback data from the set.
*
* @param action the action with which the data is associated
* @return the callback data, or null if there is none for the specified operation
*/
public CallbackData get(String action) {
return map.get(action);
}
}
/**
* Represents a callback data item.
*/
public class CallbackData {
private final String contentType;
private final String messageType;
private final String content;
/**
* Constructor
*
* @param contentType the HTTP content type (optional)
* @param messageType the callback message type (optional)
* @param content the content
*/
public CallbackData(String contentType, String messageType, String content) {
this.contentType = contentType;
this.messageType = messageType;
this.content = content;
}
/**
* Gets the callback HTTP content type, possibly null.
*/
public String getContentType() {
return contentType;
}
/**
* Gets the callback message type, possibly null.
*/
public String getMessageType() {
return messageType;
}
/**
* Gets the callback content.
*/
public String getContent() {
return content;
}
}
/**
* A tool for evaluating XPath expressions.
*/
protected class XPathTool {
private final DocumentBuilderFactory factory;
private final SimpleNamespaceContext context = new SimpleNamespaceContext();
private final XPath xpath = XPathFactory.newInstance().newXPath();
private String xml = null;
private Document doc = null;
/**
* Constructor.
*/
public XPathTool() {
factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
xpath.setNamespaceContext(context);
}
/**
* Adds a namespace.
*
* @param prefix the namespace prefix
* @param uri the namespace uri
*/
public synchronized void addNamespace(String prefix, String uri) {
context.add(prefix, uri);
}
/**
* Sets the XML content to be operated on.
*
* @param xml the XML content
*/
public synchronized void setXML(String xml) {
this.xml = xml;
this.doc = null;
}
/**
* Returns the document object.
*
* @return the document object, or null if XML has not been set
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
*/
public synchronized Document getDocument() throws ParserConfigurationException, IOException, SAXException {
if (xml == null) {
return null;
}
buildDocument();
return doc;
}
/**
* Evaluates the specified XPath expression and returns a string result. This method throws exceptions on error.
*
* @param expression the expression
* @return the result object
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws XPathExpressionException on error
*/
public synchronized String evaluate(String expression)
throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
return (String) evaluate(expression, XPathConstants.STRING);
}
/**
* Evaluates the specified XPath expression. This method throws exceptions on error.
*
* @param expression the expression
* @param returnType the return type
* @return the result object
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws XPathExpressionException on error
*/
public synchronized Object evaluate(String expression, QName returnType)
throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
buildDocument();
XPathExpression expr = xpath.compile(expression);
return expr.evaluate(doc, returnType);
}
/**
* Private helper method that builds the document object. Assumes the calling method is synchronized.
*
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
private void buildDocument() throws ParserConfigurationException, IOException, SAXException {
if (doc == null) {
if (xml == null) {
throw new IOException("XML input is null");
}
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource source = new InputSource(new StringReader(xml));
doc = builder.parse(source);
}
}
}
/**
* A NamespaceContext class based on a Map.
*/
private class SimpleNamespaceContext implements NamespaceContext {
private Map prefixMap = new HashMap<>();
private Map uriMap = new HashMap<>();
public synchronized void add(String prefix, String uri) {
prefixMap.put(prefix, uri);
uriMap.put(uri, prefix);
}
@Override
public synchronized String getNamespaceURI(String prefix) {
return prefixMap.get(prefix);
}
@Override
public Iterator getPrefixes(String uri) {
List list = new ArrayList<>();
String prefix = uriMap.get(uri);
if (prefix != null) {
list.add(prefix);
}
return list.iterator();
}
@Override
public String getPrefix(String uri) {
return uriMap.get(uri);
}
}
/**
* A VnfNotify XPathTool.
*/
protected class VnfNotifyXPathTool extends XPathTool {
public VnfNotifyXPathTool() {
addNamespace("tns", "http://org.onap.so/vnfNotify");
}
}
/**
* Helper class to make it easier to create this type.
*/
private static class CreateVnfNotificationOutputs extends CreateVnfNotification.Outputs {
public void add(String key, String value) {
Entry entry = new Entry();
entry.setKey(key);
entry.setValue(value);
getEntry().add(entry);
}
}
/**
* Helper class to make it easier to create this type.
*/
private static class UpdateVnfNotificationOutputs extends UpdateVnfNotification.Outputs {
public void add(String key, String value) {
Entry entry = new Entry();
entry.setKey(key);
entry.setValue(value);
getEntry().add(entry);
}
}
}