How to avoid encoding of < and > in a Java SOAP client - java

I am trying to call a DotNet based SOAP Webservice from my Java client and the SOAP request XML contains a CDATA xml as the value in <ser:answerFile> tag. The ideal SOAP request would look something like the below:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.intel.com/">
<soapenv:Header>
<ser:SessionHeader>
<ser:SessionId>6fdd74d0-3405</ser:SessionId>
</ser:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<ser:SaveProgressForUser>
<!--Optional:-->
<ser:userName>TestUser</ser:userName>
<!--Optional:-->
<ser:answerFile><![CDATA[<AnswerFile Version="2"><HeaderInfo></HeaderInfo><ps><p pid="e32ae8d7-017b-4d36-9c4c-09f6822362b3"><qs><q qid="b4c31241-c6c9-4013-9740-4cbc520dd10a" SelectedValue="" /><repeat c="2" guid="32418f0a-7e13-40db-872d-a42e220bfc15"><qs i="0" guid="b2f16b48-ca3d-4a06-85e8-7373ead7ccfe"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs><qs i="1" guid="cb9e34a2-ecd8-4e06-8072-a6cb682fb655"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs></repeat></qs></p></ps></AnswerFile>]]>
</ser:answerFile>
</ser:SaveProgressForUser>
</soapenv:Body>
</soapenv:Envelope>
So, in my Java client, I declare AnswerFile as a string and while invoking the Webservice I set this string using the setter.
String answerFile = "<![CDATA[<AnswerFile Version="2"><HeaderInfo></HeaderInfo><ps><p pid="e32ae8d7-017b-4d36-9c4c-09f6822362b3"><qs><q qid="b4c31241-c6c9-4013-9740-4cbc520dd10a" SelectedValue="" /><repeat c="2" guid="32418f0a-7e13-40db-872d-a42e220bfc15"><qs i="0" guid="b2f16b48-ca3d-4a06-85e8-7373ead7ccfe"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs><qs i="1" guid="cb9e34a2-ecd8-4e06-8072-a6cb682fb655"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs></repeat></qs></p></ps></AnswerFile>]]>"
However, smart that Java is, it encodes the < and > within the string to < and > and my generated request contains the <ser:answerFile> with an encoded value, something like below:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.intel.com/">
<soapenv:Header>
<ser:SessionHeader>
<ser:SessionId>6fdd74d0-3405</ser:SessionId>
</ser:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<ser:SaveProgressForUser>
<!--Optional:-->
<ser:userName>TestUser</ser:userName>
<!--Optional:-->
<ser:answerFile><![CDATA[<AnswerFile Version="2"><HeaderInfo><ps><p pid="e32ae8d7-017b-4d36-9c4c-09f6822362b3"><qs><q qid="b4c31241-c6c9-4013-9740-4cbc520dd10a" SelectedValue="" /><repeat c="2" guid="32418f0a-7e13-40db-872d-a42e220bfc15"><qs i="0" guid="b2f16b48-ca3d-4a06-85e8-7373ead7ccfe"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs><qs i="1" guid="cb9e34a2-ecd8-4e06-8072-a6cb682fb655"><q qid="7834cb57-ba6f-4063-8a02-4925079d7e04" SelectedValue="" /></qs></repeat></qs></p></ps&gt</AnswerFile>]]>
</ser:answerFile>
</ser:SaveProgressForUser>
</soapenv:Body>
</soapenv:Envelope>
So, how can I avoid this encoding and pass the answerFile value as is (With < and >).
I cannot create a document-element object structure and set, coz the setter only accepts a string as per the WSDL.
Any help will be appreciated. Please yell at me if any info is missing in the problem description.
Like I said, I havent used the Document builder approach, instead just a trivial String object to represent the CDATA XML.
An excerpt of the class can be seen below.
public static void main(String[] args) throws Exception {
try {
String username = "ServiceUser";
String password = "Help#";
String masquaradeUsername = "TestUser";
NtlmAuthenticator authenticator = new NtlmAuthenticator(username, password);
Authenticator.setDefault(authenticator);
Platform platform = new Platform();
PlatformSoap client = platform.getPlatformSoap();
String answerXML = "<![CDATA[<AnswerFile Version=\"2\"><HeaderInfo></HeaderInfo><ps><p pid=\"e32ae8d7-017b-4d36-9c4c-09f6822362b3\"><qs><q qid=\"b4c31241-c6c9-4013-9740-4cbc520dd10a\" SelectedValue=\"\" /><repeat c=\"2\" guid=\"32418f0a-7e13-40db-872d-a42e220bfc15\"><qs i=\"0\" guid=\"b2f16b48-ca3d-4a06-85e8-7373ead7ccfe\"><q qid=\"7834cb57-ba6f-4063-8a02-4925079d7e04\" SelectedValue=\"\" /></qs><qs i=\"1\" guid=\"cb9e34a2-ecd8-4e06-8072-a6cb682fb655\"><q qid=\"7834cb57-ba6f-4063-8a02-4925079d7e04\" SelectedValue=\"\" /></qs></repeat></qs></p></ps></AnswerFile>]]>";
String saveGUID = client.saveProgressForUser(masquaradeUsername, answerXML);
} catch(Exception e) {
LOGGER.info("Exception encountered####");
}
}
Hope this information helps you understand my problem better.

You have two options for solving this problem.
The first is to remove the <![CDATA[ ... ]]> escaping from the XML snippet entirely. It appears that the library class PlatformSoap already escapes the XML symbols in the content.
Since PlatformSoap excapes the string when you put it in to the XML document, if you remove the CDATA from the String, it should escape the document to:
<ser:answerFile><AnswerFile Version="2">< ......... </repeat></qs></p></ps&gt</AnswerFile>
</ser:answerFile>
That is valid XML, and, on the far side of the connection it should parse the XML properly, and restore the original content, without the CDATA.
If the far side of the connection is well-behaved, the above should just work.
On the other hand, the far side may expect to find an actual CDATA section, at which point, you should do 2 things:
file a bug against the people who do not conform to XML standards
modify the PlatformSoap class to provide a method/mechanism that forces the CDATA section around the content, and supply the un-wrapped string to that new method on PlatformSoap
In summary:
CDATA is purely a convenience wrapper that allows you to have unescaped content.
Escaping it instead of using CDATA is semantically the same, there is no difference in the logical meaning/content of the XML.
Whether the far side of the service honours XML convention or not is uncertain
if it does, then just use the escaped version (no CDATA)
if it does not, then change the far side.
if it does not, and you can't change the far side, then change your code in PlatformSoap

Related

JAXB XML generation

I want to know how I can generate the following using JAXB.
Main problem - having two namespaces in soapenv. When I make the xml and then add the soap env, it adds name spaces in different lines. This is not the way I want it
Problem 2 - instead of xmlsn="..." I want xmlns:SOME_TEXT="..." as shown below
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:fis="http://fis.certegy.cka.com/">
<soapenv:Body>
<fis:InquiryRequest>
<Version>1.0</Version>
<RequestUID>191919191919191</RequestUID>
<Station>1078686704</Station>
<TranType>40</TranType>
<Consumer>
<FirstName>Susie</FirstName>
<LastName>Smith</LastName>
<Address>
<Line1>100 59th</Line1>
<Line2>Ave NE</Line2>
<City>New York</City>
<State>NY</State>
<Zip>10021</Zip>
</Address>
<Phone>1114589658</Phone>
<EmailAddress>Your.Email#yahoo.com</EmailAddress>
<DateOfBirth>1960-01-01</DateOfBirth>
<ID>
<Type>NY</Type>
<Value>285756967</Value>
</ID>
<DeviceID>12345678000</DeviceID>
<DaysOfEmployment>9991</DaysOfEmployment>
<PayDate>2019-05-03</PayDate>
<PayFrequency>Weekly</PayFrequency>
</Consumer>
<Amount>70.00</Amount>
<CashBack>0</CashBack>
<GiftCard>0</GiftCard>
<Check>
<Micr>
<ExpansionType>1002</ExpansionType>
<Line>T861000016A100002106C</Line>
<Swiped>false</Swiped>
</Micr>
<Type>P</Type>
</Check>
</fis:InquiryRequest>
</soapenv:Body>
</soapenv:Envelope>
Please note: I am using the EclipseLink MOXY which is an extension of the JAXB and provides a lot of additional benefits compared to normal JAXB. The below answer is based on the MOXY not sure if it would work even for Standard JAXB.
I am not sure if I have understood your problem 1 correctly. Based on my understanding when you solve the problem-2 then I am guessing it should solve this as well. As it will add all the namespaces to the header of the XML in your case soapenv:Envelope.
With regards to your problem 2:
If you would like to obtain the custom prefix for your namespace URI then you can create a Map<String, String> with your prefix and namespace and pass it during the marshaling so the moxy would automatically handle and provide the prefix to header (xmlns) and your XML node.
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("http://schemas.xmlsoap.org/soap/envelope/", "soapenv");
namespaces.put("http://fis.certegy.cka.com/", "fis");
namespaces.put("Whatever your namespace", "SOME_TEXT");
jsonMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jsonMarshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaces);
Reference documentation
GitHub code

Axis client, omit empty tags from (RPC/encoded style) SOAP request xml

We have a SOAP web service from our vendor (we cannot change it) which uses "RPC/encoded" style. And we use Apache Axis (version 1.4) to generate and use the client.
There is an element in the request of one of the methods, which looks like this:
<element maxOccurs="1" minOccurs="0" name="CRD_EXPIRY" nillable="false" type="xsd:dateTime" />
So it is "omittable", but not "nillable". So when I leave this param empty (when we do not call setCRD_EXPIRY at all), I expect there will be no CRD_EXPIRY tag in request XML at all.
But Axis engine, still puts this tag in the request, with nil="true" attribute:
<CRD_EXPIRY xsi:nil="true" xsi:type="xsd:dateTime"/>
Further, I have dug into the sources, and found these lines in org.apache.axis.encoding.ser.BeanSerializer class, which seems to do the trick:
// . . .
if (propValue == null) {
// . . .
// if meta data says minOccurs=0, then we can skip
// it if its value is null and we aren't doing SOAP
// encoding.
if (isOmittable && !isEncoded) {
continue;
}
}
context.serialize(qname,
null,
propValue,
xmlType, javaType);
// . . .
It looks like for "encoded" style web services empty parameters are always present in request XML with nil values. Even if they are not "nillable".
Am I true? If yes, why is it like this (does not look like a bug)?
And mainly, how can I achieve that empty params do not appear in request XML?
Thanks

Retrieve value of attribute using XPath

I am trying to retrieve the value of an attribute from an xmel file using XPath and I am not sure where I am going wrong..
This is the XML File
<soapenv:Envelope>
<soapenv:Header>
<common:TestInfo testID="PI1" />
</soapenv:Header>
</soapenv:Envelope>
And this is the code I am using to get the value. Both of these return nothing..
XPathBuilder getTestID = new XPathBuilder("local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']/*[local-name(.)='TestInfo'])");
XPathBuilder getTestID2 = new XPathBuilder("Envelope/Header/TestInfo/#testID");
Object doc2 = getTestID.evaluate(context, sourceXML);
Object doc3 = getTestID2.evaluate(context, sourceXML);
How can I retrieve the value of testID?
However you're iterating within the java, your context node is probably not what you think, so remove the "." specifier in your local-name(.) like so:
/*[local-name()='Header']/*[local-name()='TestInfo']/#testID worked fine for me with your XML, although as akaIDIOT says, there isn't an <Envelope> tag to be seen.
The XML file you provided does not contain an <Envelope> element, so an expression that requires it will never match.
Post-edit edit
As can be seen from your XML snippet, the document uses a specific namespace for the elements you're trying to match. An XPath engine is namespace-aware, meaning you'll have to ask it exactly what you need. And, keep in mind that a namespace is defined by its uri, not by its abbreviation (so, /namespace:element doesn't do much unless you let the XPath engine know what the namespace namespace refers to).
Your first XPath has an extra local-name() wrapped around the whole thing:
local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']
/*[local-name(.)='TestInfo'])
The result of this XPath will either be the string value "TestInfo" if the TestInfo node is found, or a blank string if it is not.
If your XML is structured like you say it is, then this should work:
/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='TestInfo']/#testID
But preferably, you should be working with namespaces properly instead of (ab)using local-name(). I have a post here that shows how to do this in Java.
If you don't care for the namespaces and use an XPath 2.0 compatible engine, use * for it.
//*:Header/*:TestInfo/#testID
will return the desired input.
It will probably be more elegant to register the needed namespaces (not covered here, depends on your XPath engine) and query using these:
//soapenv:Header/common:TestInfo/#testID

kSOAP XmlPullParserException

I'm stumped on a problem trying to consume a ColdFusion SOAP service in Android using kSOAP2. Here is my java code for invoking a test method I've written in ColdFusion (which only returns a string):
private static final String NAMESPACE = "http://www.sub.tv/MyService.cfc?WSDL";
private static String URL = "http://www.sub.tv/MyService.cfc";
private static final String METHOD_NAME = "TestMethod";
private static final String SOAP_ACTION = "http://www.sub.tv/MyService.cfc?method=TestMethod";
public void GetData() {
SoapPrimitive resultstring = null;
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
PropertyInfo inputArgs = new PropertyInfo();
inputArgs.setName("ID");
inputArgs.setValue(1234);
inputArgs.setType(Integer.class);
request.addProperty(inputArgs);
SoapSerializationEnvelope soapenvelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
soapenvelope.dotNet = false;
soapenvelope.setOutputSoapObject(request);
AndroidHttpTransport httptransport = new AndroidHttpTransport(URL);
//httptransport.debug = true;
try {
httptransport.call(SOAP_ACTION, soapenvelope);
resultstring = (SoapPrimitive) soapenvelope.getResponse();
} catch (Exception e) {
Log.d(DEBUG, e.getMessage());
}
}
And here is the ColdFusion test method I've written that only returns a string:
<cfcomponent displayname="test_web_service" namespace="http://www.sub.tv">
<cffunction name="TestMethod" returnType = "string" access="remote" description="Test Method">
<cfargument name="ID" type="numeric">
<cfreturn "hello" />
</cffunction>
</cfcomponent>
The error I'm getting when I execute the above Java code is as follows:
org.xmlpull.v1.XmlPullParserException: expected: START_TAG {http://schemas.xmlsoap.org/soap/envelope/}Envelope (position:START_TAG <html>#2:44 in java.io.InputStreamReader#40ff5440)
I suspect the cause of the problem is perhaps the URL I've specified in the SOAP_ACTION but, as far as I know, that's the correct way to invoke a ColdFusion SOAP web service method. Executing that URL in the browser returns the expected results. I've tried excluding the method call in the query string of that URL but I still get the same error.
Any help would be much appreciated.
Thanks for your time,
Tony
Try removing the display name and the namespace. I know neither of these are needed when exposing a web service via CFML. I also suspect that you have an error you're not seeing. Note that in your error it has:
org.xmlpull.... (position:START_TAG <html>#2:44 in java.io.InputStreamReader#40ff5440)
That html tag suggests to me that your web service is throwing an error. When CF does this it outputs HTML. Knowing that, here are a few suggestions. From a browser:
1) access your service directly in the browser: http://www.sub.tv/MyService.cfc. Login to the CF admin and make sure that you see your CFC's documentation.
2) access your WSDL: http://www.sub.tv/MyService.cfc?wsdl. You should see your various functions exposed.
3) access your test function: http://www.sub.tv/MyService.cfc?method=TestMethod&id=123
Actually, since www.sub.tv is public and MyService.cfc is available I tested all of the above for you and it looks like your CFC is good and not throwing errors.
I made a quick test of your CFC:
<cfset test = CreateObject("WebService", "http://www.sub.tv/MyService.cfc?WSDL") />
<cfdump var="#test.TestMethod(123)#" />
This outputs "hello", which is what you'd expect if your Web Service is functioning correctly. This suggests to me that there's an issue in how you're calling it from Android. I've not done much work with Web Services in Java, so I can only be so helpful here.
However, I noticed that your WSDL defines the test method argument as a Double. This is normal for CF since a numeric type can hold any type of number. However, your example code above shows the following:
inputArgs.setType(Integer.class);
Try changing that to Double.class (or whatever this should be in Java to match the argument type.
That's about all I've got for you. Good luck!
I suspect the issue is that you are calling a web service via SOAP but the response is not in the expected format (XML). If you look at the generated output from your web service call http://www.sub.tv/MyService.cfc?method=TestMethod&id=123 you see this:
<wddxPacket version='1.0'>
<header/>
<data>
<string>hello</string>
</data>
</wddxPacket>
This is because By default, ColdFusion serializes all return types (including simple return types), except XML, into WDDX format... from the CFFunction documentation here
Try specifying a returnType of XML in your ColdFusion function and see if that works. You may still need to tweak the output to get what kSOAP is expecting. I don't know the XML format it wants but this should get you started.
Change your CFFunction to something like this:
<cffunction name="TestMethod" returnType="xml" access="remote" description="Test Method">
<cfargument name="ID" type="numeric">
<cfset var xml = "">
<cfxml variable="xml">
<test>hello</test>
</cfxml>
<cfreturn xml>
</cffunction>

How to parse soap response message?

I want to parse the following xml snippet. Can anyone help me out in this. I am not getting how to parse through the elements and all and just retrieve the and EmployeeId nodes and its values
<soapenv:Envelope xmlns:soapenv="http://schemas.xxx/yyy" xmlns:idi="http://xxxx/">
<soapenv:Header/>
<soapenv:Body>
<idi:retrieve>
<requestXML>
<CustomerNumber>111</CustomerNumber>
<EmployeeId>222</EmployeeId>
</requestXML>
</idi:retrieve>
</soapenv:Body>
</soapenv:Envelope>
Please see the xml above and give some hinte to parse this.
Thank you
You should use a framework that knows to create an object model from a WSDL and do the parsing for you - that is the best course of action.
Just google on any of these:
Axis2
JAX-WS
JAX-RPC
Choose one and use it.

Categories

Resources