I have a destination which sends an HL7 v2 message to a WCF webservice. I've managed to get the message correctly to the webservice (after fixing various encoding problems) and I can confirm that the message is reaching the WCF endpoint correctly. However, after much hunting around in the forums and documentation, I am unable to correctly parse the ACK we receive back to indicate when an error has occurred.
The response coming back from the webservice looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header/>
<s:Body>
<ProcessMessageResponse xmlns="http://www.bluewire-technologies.com/webservices">
<ProcessMessageResult>
MSH|^~\&|Epro|RGR|||||ACK||D||||||GBR|ASCII|
MSA|AE||Empty message|
ERR|^^^100|
</ProcessMessageResult>
</ProcessMessageResponse>
</s:Body>
</s:Envelope>
The response contains an ACK in the ProcessMessageResult element. How to I extract this ACK from the response and pass it as the output of the destination? Will Mirth automatically parse the ACK and determine that there was an error?
I had thought that I needed some kind of transformer on the destination (called 'SOAP') and use something along the lines of:
var xml = responseMap.get('SOAP').getMessage();
and then extract the ProcessMessageResponse element but responseMap.get('SOAP') returns null so that seems the wrong approach.
I've now solved part of this thanks to an answer on the Mirth forums.
To summarize, I use the following postprocessor to extract the ack and update the status:
var s = new Namespace('http://schemas.xmlsoap.org/soap/envelope/');
var bw = new Namespace('http://www.bluewire-technologies.com/webservices');
var response = new XML($r('SOAP').getMessage());
var ack = new XML(SerializerFactory.getHL7Serializer().toXML(response.s::Body.bw::ProcessMessageResponse.bw::ProcessMessageResult.toString()));
var ackCode = ack.MSA['MSA.1']['MSA.1.1'].toString();
if(ackCode == 'AE')
{
var errorMessage = ack.MSA['MSA.3']['MSA.3.1'].toString();
var messageController = com.mirth.connect.server.controllers.DefaultMessageObjectController.create();
var channelStatisticsController = com.mirth.connect.server.controllers.DefaultChannelStatisticsController.create();
messageObject.getContext().put("replace", "true"); // yuk - this is to make setError below work.
messageController.setError(messageObject, null, errorMessage, null, null);
channelStatisticsController.decrementSentCount(new java.lang.String(channelId));
}
Not pretty, but it works...
Related
I am trying to make a POST request to webpage that expects the --data field to be filled with some data to be processed. I'm pretty much trying to recreate this curl request, but with UnityWebRequest.
curl -X POST http://localhost:8000/clic/say?text=Make+the+gene+set --data '{"geneSetMembers":["UST"],"geneSetName":"selection0"}'
The UnityWebRequest documentation mentions that GET requests don't set any flags other than the url, but it's not clear if no other custom options exist for posts. Is there some way to format a WWWform or something that will hold the data such that the server will recognize it?
var form = new WWWForm();
// some way to plug in the jsonified data to the form
webRequest = UnityWebRequest.Post(url + route + to_say, form);
webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Content-Type", "application/json");
webRequest.SendWebRequest();
// etc etc
I've tried just giving the form a field named "data" a la
form.AddField("data", "{ \"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}");
but the server does not like it, saying it "got error Invalid JSON literal name: data" So clearly that's the wrong syntax for it
EDIT: put lines in the same order they were in original code. Sorry, I have commented lines between them
Maybe your server doesn't like to receive the data as a field called data.
This ofcourse depends totally on the PHP code we don't see since you didn't share that part. b
But at least I can tell you that --data or also simply -d in curl refer to the entire data section and is not a field called data.
You could try to instead use a MultiPartFormDataSection passing just the data itself without a specific field name
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var form = new List<IMultiFormPart>{ new MultiPartFormDataSection(data) };
webRequest = UnityWebRequest.Post(url + route + to_say, form);
yield return webRequest.SendWebRequest();
which is now sent as content-type multipart/form-data though ...
Another alternative if your server really needs to receive a content-type application/json might be to "manually" compose the request e.g. like
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var request = new UnityWebRequest(url + route + to_say, "POST");
var bodyRaw = Encoding.UTF8.GetBytes(data);
request.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
request.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
Though of you look close now this seems actually not to be the case since if you read the man curl
(HTTP) Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. This will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded
which is actually exactly the default content type used by the simple string version of UnityWebRequest.Post.
So thinking about it it should actually be as simple as using the pure string version of UnityWebRequest.Post:
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var request = UnityWebRequest.Post(url + route + to_say, data);
yield return request.SendWebRequest();
I am trying to consume this soap service: http://testws.truckstop.com:8080/v13/Posting/LoadPosting.svc?singleWsdl with node-soap, but the client is mangling the namespaces and I have been unable to find a working solution.
I believe the answer is to either add a namespace to the soap envelope, or overwrite the soap envelope.
Using Soap UI, the request should look like:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v11="http://webservices.truckstop.com/v11"
xmlns:web="http://schemas.datacontract.org/2004/07/WebServices">
<soapenv:Header/>
<soapenv:Body>
<v11:GetLoads>
<v11:listRequest>
<web:IntegrationId>integrationId</web:IntegrationId>
<web:Password>password</web:Password>
<web:UserName>username</web:UserName>
</v11:listRequest>
</v11:GetLoads>
</soapenv:Body>
</soapenv:Envelope>
However, when I do:
client = soap.createClient(url);
let query = {
listRequest: {
Password: password,
UserName: username,
IntegrationId: integrationId
}
};
let results = client.GetLoads(query);
The client generates this xml:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:tns="http://webservices.truckstop.com/v11"
xmlns:q1="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q2="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q3="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q4="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q5="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q6="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q7="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q8="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q9="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q10="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q11="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q12="http://schemas.datacontract.org/2004/07/WebServices.Objects">
<soap:Body>
<GetLoads xmlns="http://webservices.truckstop.com/v11">
<listRequest>
<ns1:IntegrationId>integrationId</ns1:IntegrationId>
<ns1:Password>password</ns1:Password>
<ns1:UserName>usernam</ns1:UserName>
</listRequest>
</GetLoads>
</soap:Body>
</soap:Envelope>
This fails because IntegrationId, Password and UserName need http://schemas.datacontract.org/2004/07/WebServices, but the namespace isn't referenced in the envelope.
I've tried updating the client to add the namespace as suggested here:
client.wsdl.definitions.xmlns.ns1 = "http://schemas.datacontract.org/2004/07/WebServices";
client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();
I can see the namespace in client.wsdl.xmlnInEnvelope, but it doesn't seem to change the actual generated xml.
Is there another step required to refresh the client to use the updated envelope?
I also tried overriding the root element as shown here:
var wsdlOptions = {
//namespaceArrayElements: "xmlns:ns1=http://schemas.datacontract.org/2004/07/WebServices"
"overrideRootElement": {
"namespace": "xmlns:tns",
"xmlnsAttributes": [{
"name": "xmlns:tns",
"value": "http://webservices.truckstop.com/v11"
}, {
"name": "xmlns:ns1",
"value": "http://schemas.datacontract.org/2004/07/WebServices"
}]
}
};
this.loadPostClient = soap.createClient(this.tsConfig.loadPostUrl, wsdlOptions);
This changes the root body element:
<soap:Body>
<xmlns:tns:GetLoads
xmlns:tns="http://webservices.truckstop.com/v11"
xmlns:ns1="http://schemas.datacontract.org/2004/07/WebServices">
<listRequest>
<ns1:IntegrationId>integrationId</ns1:IntegrationId>
<ns1:Password>password</ns1:Password>
<ns1:UserName>username</ns1:UserName>
</listRequest>
</xmlns:tns:GetLoads>
</soap:Body>
But the remote server doesn't understand.
Thank you for reading!
This answer was correct all along
It wasn't working for me due to autocomplete and similar fields
client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();
Should have been:
client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap();
I left out an s and was setting xmlnInEnvelope instead of xmlnsInEvelope
It's been a few years, but I ran into a similar need of adding custom attributes to the soap envelope and wanted to give an alternative.
As of this writing, that _xmlnsMap() is a private method on the WSDL class so you can use it at your own risk. I always take private methods as subject to change from the developer without any notice to the library consumers so I wanted to find another way and turns out its possible.
TL;DR - Create your own WSDL class instance and pass it to your own Client class instance.
Use the open_wsdl method to bring in your WSDL
Use the callback to build your own custom attributes in a concatenated string.
Assign the attributes to the public xmlnsInEnvelope property.
return the updated WSDL instance (I used a promise).
const fetchWSDL = new Promise<WSDL>((resolve, reject) => {
// method that returns a WSDL instance from a url/file path
open_wsdl(this.wsdl, (err: any, wsdl?: WSDL) => {
// Build custom attributes
if (wsdl && wsdl.definitions.xmlns) {
const xmlns: { [key: string]: string } = {
[your namespaces]: 'values',
};
// turn your custom attributes map into a single concatenated string
let str = '';
for (const alias in xmlns) {
const ns = xmlns[alias];
str += ' xmlns:' + alias + '="' + ns + '"';
}
// Leverage public attribute on WSDL instance to apply our custom attributes
wsdl.xmlnsInEnvelope = str;
resolve(wsdl);
}
reject(err);
});
});
Use the updated WSDL instance to create your own client.
NOTE: the createClient method is just a convenience wrapper for creating a WSDL instance and returning a new Client instance.
const ModifiedWSDL = await fetchWSDL;
// Create client with our modified WSDL instance
this.client = new Client(ModifiedWSDL)
// adjust your Client instance as needed
A bit more code that the OP, but hopefully more in line with node-soap types and safer to use if you plan to upgrade.
I have a SpringWS inplementation with below enpoint implementation
#PayloadRoot(namespace="http://college.com/schema/get_XML_Request/v2",localPart="get_XML_Request")
#ResponsePayload
public JAXBElement<GetStudentResponseType> handleStudentXML(#RequestPayload JAXBElement<GetStudentXMLRequestType> SoapRequest)throws Exception
{
String xmlResponse = "";
com.college.get_student_xml_response.v2.ObjectFactory objectFactory = new com.company.schema.get_student_xml_response.v2.ObjectFactory();
com.college.schema.get_student_xml_response.v2.GetResponseType resType = objectFactory.createGetResponseType();
return objectFactory.createGetStudentResponse(resType);
}
Here my objective is to log the request which coming to my webservice and response which the web service sent back in a table. Is it possible to get the SoapRequest/Soapresponse (In Soapformat) from the above method as a String.Here am able to get the payload, but i need to log with entire SoapRequest(with soapenvelope,body) Please anyone advice on this.
Have a look at the SoapEnvelopeLoggingInterceptor which logs the whole SOAP
Envelope including headers. So basically you can extend it to add the saving to the database functionality.
I have a Mirth channel that set up as a web service listener, it receives an ID, build an HL7 query message and send this query and eventually get back an HL7 response.
Channel Name: QueryChanel
Source Connector Type: Web Service Listener
Destination Connector Name: QueryToVista
Destination connector Type:LLP Sender.
This is the typical HL7 response I receive back from my query is as follow:
MSH|~|\&|VAFC RECV|FACILITY|VAFC TRIGGER||20121011141136-0800||ADR~A19|58269|D|2.4|||NE|NE|USA
MSA|AA|1234|
QRD|20121011051137|R|I|500000001|||1^ICN|***500000001***|ICN|NI|
EVN|A1|20121004064809-0800||A1|0^^^^^^^^USVHA\\0363^L^^^NI^TEST FACILITY ID\050\L|20121004064809-0800|050
PID|1|500000001V075322|500000001V075322^^^USVHA\\0363^NI^VA FACILITY ID\050\L~123123123^^^USSSA\\0363^SS^TEST FACILITY ID\050\L~9^^^USVHA\\0363^PI^VA FACILITY ID\050\L||JOHN^DOE^^^^^L|""|19800502|M||""|""^""^""^""^""^^P^""^""~^^""^""^^^N|""|""|""||S|""||123123123|||""|""||||||""||
PD1|||SOFTWARE SERVICE^D^050
ZPD|1||||||||||||||||""
I can get all the above to return if I set my Source's Response From parameter to QueryToVista
However, I want to return only the value 500000001 from the above message. I've tried to play around with the transformer in the QueryChanel destination without success.
Update:
I tried to add a javascriptwriter connector after the QueryToVista connector in the same channel as follow:
var destination = responseMap.get('QueryToVista');
var responseMessage = destination.getMessage();
//Fails with following error: TypeError: Cannot read property "QRD.4" from undefined
var customack = ResponseFactory.getSuccessResponse(responseMessage['QRD']['QRD.4'] ['QRD.4.1'].toString())**
//work but send the whole HL7 message
var customack = ResponseFactory.getSuccessResponse(responseMessage.toString())**
responseMap.put('Barcode', customack);
I can't seem to use the normal transformation to retrieve the element at all.
Thank you.
You're on the right track, but your update illustrates a couple of issues. However, your basic approach of using two destinations is valid, so long as "Synchronize channel" is checked on the Summary tab.
Issue 1
In your example, the HL7 response you are wanting to parse is in pipe delimited HL7 form. In order to access the elements using E4X notation (eg. responseMessage['QRD']['QRD.4']['QRD.4.1']) you must first convert it into an E4X XML object. This can be done in two steps.
Convert the pipe delimited HL7 string into an XML string.
Convert the XML string into an E4X XML object
In a Javascript transformer of the JavaScript Writer (not the Javascript Writer script itself)
var response = responseMap.get("QueryToVista");
var responseStatus = response.getStatus();
// Get's the pipe delimited HL7 string
var responseMessageString = response.getMessage();
if (responseStatus == "SUCCESS")
{
// converts the pipe delimited HL7 string into an XML string
// note: the SerializeFactory object is available for use in transformer
// scripts, but not in the Javascript destination script itself
var responseMessageXMLString = SerializerFactory.getHL7Serializer(false,false,true).toXML(responseMessageString);
// convert the XML string into an E4X XML object
var responseMessageXMLE4X = new XML(responseMessageXMLString);
// grab the value you want
var ack_msg = responseMessageXMLE4X['QRD']['QRD.4']['QRD.4.1'].toString();
channelMap.put('ack_msg', ack_msg)
}
else
{
// responseStatus probably == "FAILURE" but I'm not sure of the full range of possibilities
// take whatever failure action you feel is appropriate
}
Edit**
I don't believe there is an Issue 2. After reviewing your own approach, I played a bit further, and believe I have confirmed that your approach was indeed correct for generating the SOAP reponse. I'm editing this section to reflect simpler code that still works.
In the Javascript Writer script
var barcode = channelMap.get('ack_msg');
var mirthResponse = ResponseFactory.getSuccessResponse(barcode);
responseMap.put('Barcode', mirthResponse);
Thank you very much csj,
I played around and got mine to work and looking at your solution, you pointed out my bottle neck to the issue as well which is the XML part, I did not realize you have to cast it into XML as per the new XML when you already call toXML function :)
Here is my script, though basic I thought I post it up for anyone find it useful down the road.
var destination = responseMap.get('QueryToVista');
var responseMessage = destination.getMessage();
var Xmsg = new XML(SerializerFactory.getHL7Serializer().toXML(responseMessage));
var xml_msg = '<?xml version="1.0" encoding="utf-8" ?>'+
'<XML><Patient Name="'+Xmsg['PID']['PID.5']['PID.5.1']+
'" Barcode="'+Xmsg['QRD']['QRD.8']['QRD.8.1']+'" /></XML>';
var sResp = ResponseFactory.getSuccessResponse(xml_msg)
responseMap.put('Response', sResp);
I don't understand why XmlSlurper is apparently not working on the result.
import groovyx.net.http.*
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
def String WSDL_URL = ...
def http = new HTTPBuilder( WSDL_URL , ContentType.XML )
String soapEnvelope =
"""<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetTerritories xmlns="...">
<State>AZ</State>
<ZipCode>85203</ZipCode>
</GetTerritories>
</soap12:Body>
</soap12:Envelope>"""
http.request( POST, XML ) {
headers."Content-Type" = "application/soap+xml; charset=utf-8"
headers."Accept" = "application/soap+xml; charset=utf-8"
body = soapEnvelope
response.success = { resp, xml ->
println "XML was ${xml}"
println "Territories were ${xml.Territories}"
println "State were ${xml.Territories.State}"
println "City was ${xml.Territories.Territory.City}"
println "County was ${xml.Territories.Territory.County}"
}
response.failure = { resp, xml ->
xml
}
}
leads to
XML was <Territories><State>AZ</State><ZipCode>85203</ZipCode><Territory><City>Mesa</City><County>Maricopa</County>...</Territory></Territories>
Territories were
State were
City was
County was
UPDATE: Thanks to John Wagenleitner's insight, I did a little more digging.
When I add that assert, I see an issue:
assert "Territories" == xml.name()
| | |
| | Envelope
| <Territories><State>AZ</State><ZipCode>85203</ZipCode</Territories>
false
Changing the request parameters from POST, XML to POST, TEXT is revealing:
XML was <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetTerritoriesResponse xmlns="...">
<GetTerritoriesResult><Territories><State>AZ</State><ZipCode>85203</ZipCode><Territory><City>Mesa</City><County>Maricopa</County>...</Territory></Territories>
</GetTerritoriesResult>
</GetTerritoriesResponse>
</soap:Body>
</soap:Envelope>
...
So it looks like the XmlSlurper, when the variable is being printed out, is throwing away the SOAP stuff and evaluating the innermost node () while not actually navigating to that node. Is this expected behavior?
I have been unable to find a a more complete and modern SOAP call and parse using httpBuilder, so I assumed XML would be the right content type. But it looks like I'll just have to accept TEXT and parse the body myself, which seems lame. Is there a better way of handling SOAP responses with httpBuilder?
I would recommend printing the raw text of the response:
println "XML was ${resp.data.text}"
Assuming that the printed XML line is what you expect (though odd since there is no Envelope or Body nodes), then you should be able to remove Territories from your references to xml. When parsed with XmlSlurper the root node is the GPathResult.
assert "Territories" == xml.name()
println "State were ${xml.State.text()}"
println "City were ${xml.Territory.City.text()}"
println "County were ${xml.Territory.County.text()}"
Also, just wanted to point out that the SOAP 1.2 media type is "application/soap+xml".
UPDATE:
So it looks like the XmlSlurper, when the variable is being printed
out, is throwing away the SOAP stuff and evaluating the innermost node
() while not actually navigating to that node. Is this expected
behavior?
Yes, the toString() method for a GPathResult just prints all text nodes and not the actual elements or attributes. With HTTPBuilder you can print out the raw response text by using:
println resp.data.text
I have been unable to find a a more complete and modern SOAP call and
parse using httpBuilder, so I assumed XML would be the right content
type. But it looks like I'll just have to accept TEXT and parse the
body myself, which seems lame. Is there a better way of handling SOAP
responses with httpBuilder?
The ContentType.XML is fine, the issue is with how the SOAP response that your web service returns is formed. The web service is sending back the Territories results as an encoded string in the GetTerritoriesResult element and not as part of the actual XML response that HTTPBuilder automatically parses for you (this is not a problem with the way HTTPBuilder is handling it). Because the data you really want is in that encoded string you will need to parse the text node of the GetTerritoriesResult yourself.
response.success = { resp, xml ->
println "XML was ${resp.data.text}"
def territories = new XmlSlurper().parseText(
xml.Body.GetTerritoriesResponse.GetTerritoriesResult.text()
)
println "State were ${territories.State}"
println "City was ${territories.Territory.City}"
println "County was ${territories.Territory.County}"
}