Modify response of web service with JAX-WS - web-services

How can I modify the namespace of the response like this:
old response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:GetAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</ns2:GetAmountResponse>
</soap:Body>
</soap:Envelope>
new response wanted :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
</soap:Body>
</soap:Envelope>
I want to remove the ns2 namespce prefix.

In the first case, the GetAmountResponse is in namespace http://ws.dsi.otn.com/dab while etat and montant are in a default (empty) namespace.
In the new message you want, GetAmountResponse, etat and montant are all in namespace http://ws.dsi.otn.com/dab.
The namespaces can be controlled from the namespaces of your classes. Use the same namespace in all and you will have them in the same namespace, leave classes with defaults and they default to empty namespace.
For example, if you were to have something like this in your web service class:
#WebMethod
public
#WebResult(name = "getAmountResponse", targetNamespace = "http://ws.dsi.otn.com/dab")
AmountResponse getAmount(
#WebParam(name = "getAmountRequest", targetNamespace = "http://ws.dsi.otn.com/dab") AmountRequest request) {
AmountResponse response = new AmountResponse();
response.setEtat(0);
response.setMontant(500.0);
return response;
}
with a response class like this:
#XmlRootElement
public class AmountResponse {
private int etat;
private double montant;
// getter and setters omitted
}
you will end up with the first type of soap message.
But if you change the response class to look like this instead:
#XmlRootElement(namespace = "http://ws.dsi.otn.com/dab")
#XmlAccessorType(XmlAccessType.NONE)
public class AmountResponse {
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private int etat;
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private double montant;
// getters and setter omitted
}
you will bring all tags in the same namespace and you get something equivalent to the new type of message you want. I said equivalent because I don't think you will get exactly this:
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
It's more likely to get something like this instead:
<ns2:getAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<ns2:etat>0</ns2:etat>
<ns2:montant>500.0</ns2:montant>
</ns2:getAmountResponse>
It's the same "XML meaning" for both messages although they don't look the same.
If you absolutely want it to look like that, I think you will have to go "low level" and use something like a SOAP handler to intercept the response and modify it. But be aware that it won't be a trivial task to change the message before it goes on the wire.

logical handler are enough to transform to the message as expected :
package com.ouertani.slim;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;
/**
*
* #author ouertani
*/
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> {
#Override
public boolean handleMessage(LogicalMessageContext messageContext) {
/// extract state and amount
int state = 0;
double amount = 200.0;
transform(messageContext, state, amount);
return false;
}
public boolean handleFault(LogicalMessageContext messageContext) {
return true;
}
public void close(MessageContext context) {
}
private void transform( LogicalMessageContext messageContext, int etat, double montant){
LogicalMessage msg = messageContext.getMessage();
String htom = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<soap:Body>"+
"<GetAmountResponse xmlns=\"http://ws.dsi.otn.com/dab\">"+
"<etat>"+etat+"</etat>"+
"<montant>"+montant+"</montant>"+
"</GetAmountResponse>"+
"</soap:Body>"+
"</soap:Envelope>";
InputStream is = new ByteArrayInputStream(htom.getBytes());
Source ht = new StreamSource(is);
msg.setPayload(ht);
}
}

This is a very old question, still it is yet to be effectively answered. This week I faced a very similar problem. My application is invoking a Soap web-service provided by a legacy system whose XML is response syntactically wrong with some empty characters (line break, or tabs or white spaces) before XML declaration. In my scenario I could not change the legacy system to fix its response so changing the response before parsing was the only alternative I was left with.
Here is my solution:
I have added the following maven dependencies to my application:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.0</version>
</dependency>
Then I have registered a Java SPI custom implementation of “com.oracle.webservices.impl.internalspi.encoding.StreamDecoder”. This class is invoked immediately before the XML parse with the corresponding response InputStream, so at this point you can read the response InputStream or wrap/proxy it and make any change to jax-ws response before parsing. In my case I just remove some invisible characters before first visible character.
My StreamDecoder SPI implementation:
package sample.streamdecoder;
import com.oracle.webservices.impl.encoding.StreamDecoderImpl;
import com.oracle.webservices.impl.internalspi.encoding.StreamDecoder;
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.message.AttachmentSet;
import com.sun.xml.ws.api.message.Message;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public class MyStreamDecoder implements StreamDecoder {
//JAX-WS default implementation
private static final StreamDecoderImpl streamDecoder = new StreamDecoderImpl();
#Override
public Message decode(InputStream inputStream, String charset, AttachmentSet attachmentSet, SOAPVersion soapVersion) throws IOException {
//Wrapping inputStream
InputStream wrapped = wrapInputStreamStrippingBlankCharactersBeforeXML(inputStream, charset);
//Delegating further processing to default StreamDecoder
return streamDecoder.decode(wrapped, charset, attachmentSet, soapVersion);
}
private InputStream wrapInputStreamStrippingBlankCharactersBeforeXML(InputStream inputStream, String charset) throws IOException {
int WHITESPACE = (int) Charset.forName(charset).encode(" ").get();
int LINE_BREAK = (int) Charset.forName(charset).encode("\n").get();
int TAB = (int) Charset.forName(charset).encode("\t").get();
return new InputStream() {
private boolean xmlBegin = true;
#Override
public int read() throws IOException {
int read = inputStream.read();
if (!xmlBegin) {
return read;
} else {
while (WHITESPACE == read
|| LINE_BREAK == read
|| TAB == read) {
read = inputStream.read();
}
xmlBegin = false;
}
return read;
}
};
}
}
In order to register it, just create a file “META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder” named “” and write the fully qualified name of your SPI implementation on the first line like that:
Content of file META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder :
sample.streamdecoder.MyStreamDecoder
Now every response will be passed to you implementation before parse.

Related

How to use AWSRequestSigningApacheInterceptor with AWS SDK2

I am trying to use REST calls to Neptune SPARQL on existing Java code which already uses Apache HTTP clients. I'd like to not mix and match AWS SDK1 and SDK2 (which I use for the S3 portion of loading owl to Neptune).
I see these solutions:
AWSRequestSigningApacheInterceptor that works with SDK1, but can't find the equivalent in SDK2.
aws-request-signing-apache-interceptor on github for building an adaptor class so it can be used in SDK 2 with mix-and-match SDK 1 & 2
javaquery/Examples where Vicky Thakor has gone even more generic and just implemented the V4 signing for any Java REST implementation
But none of these is what I expected: an AWS or Apache implmentation of an Apache Interceptor for AWS SDK 2.
Is there such a thing? or is one of the above solutions the best available at the moment?
Here is some minimal code to make a few different authenticated REST requests to the ElasticSearch API (not Neptune SPARQL, but it's all REST).
pom.xml:
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<!-- version number is not needed due to the BOM below -->
</dependency>
<!-- below is needed for this issue: https://github.com/aws/aws-sdk-java-v2/issues/652 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.11</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<!-- version number is not needed due to the BOM below -->
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.7.36</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
And here's the java:
import org.json.JSONObject;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
import software.amazon.awssdk.http.*;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.utils.StringInputStream;
import java.io.*;
public class ElasticSearch implements Closeable {
private static final String HOST = "my-elasticsearch-3490jvoi2je3o.us-east-2.es.amazonaws.com";
private Aws4SignerParams params = Aws4SignerParams.builder()
.awsCredentials(DefaultCredentialsProvider.create().resolveCredentials())
.signingName("es") // "es" stands for elastic search. Change this to match your service!
.signingRegion(Region.US_EAST_2)
.build();
private Aws4Signer signer = Aws4Signer.create();
SdkHttpClient httpClient = ApacheHttpClient.builder().build();
/** #param path should not have a leading "/" */
private HttpExecuteResponse restRequest(SdkHttpMethod method, String path) throws IOException {
return restRequest(method, path, null);
}
private HttpExecuteResponse restRequest(SdkHttpMethod method, String path, JSONObject body)
throws IOException {
SdkHttpFullRequest.Builder b = SdkHttpFullRequest.builder()
.encodedPath(path)
.host(HOST)
.method(method)
.protocol("https");
if (body != null) {
b.putHeader("Content-Type", "application/json; charset=utf-8");
b.contentStreamProvider(() -> new StringInputStream(body.toString()));
}
SdkHttpFullRequest request = b.build();
// now sign it
SdkHttpFullRequest signedRequest = signer.sign(request, params);
HttpExecuteRequest.Builder rb = HttpExecuteRequest.builder().request(signedRequest);
// !!!: line below is necessary even though the contentStreamProvider is in the request.
// Otherwise the body will be missing from the request and auth signature will fail.
request.contentStreamProvider().ifPresent(c -> rb.contentStreamProvider(c));
return httpClient.prepareRequest(rb.build()).call();
}
public void search(String indexName, String searchString) throws IOException {
HttpExecuteResponse result = restRequest(SdkHttpMethod.GET, indexName+"/_search",
new JSONObject().put("query",
new JSONObject().put("match",
new JSONObject().put("txt",
new JSONObject().put("query", searchString)))));
System.out.println("Search results:");
System.out.println(new JSONObject(result.responseBody()));
}
/** #return success status */
public boolean createIndex(String indexName) throws IOException {
if (indexName.contains("/")) {
throw new RuntimeException("indexName cannot contain '/' character");
}
HttpExecuteResponse r = restRequest(SdkHttpMethod.PUT, indexName);
System.out.println("PUT /"+indexName + " response code: " + r.httpResponse().statusCode());
printInputStream(r.responseBody().get());
return r.httpResponse().isSuccessful();
}
private void printInputStream(InputStream is) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String readLine;
while (((readLine = br.readLine()) != null)) System.out.println(readLine);
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean postDoc(String indexName, String docId, JSONObject docBody) throws IOException {
HttpExecuteResponse response = restRequest(
SdkHttpMethod.PUT,
String.format("%s/_doc/%s", indexName, docId),
docBody
);
System.out.println("Index operation response:");
printInputStream(response.responseBody().get());
return response.httpResponse().isSuccessful();
}
#Override
public void close() throws IOException {
httpClient.close();
}
}
So, I settled on the second option with an important caveat: it does not handle AWS_SESSION_TOKEN. This is a simple fix. I've posted it along with the original answer at http://github.com/awslabs/aws-request-signing-apache-interceptor/
There's a new maintained fork of the archived awslabs aws-request-signing-apache-interceptor. It was upgraded to AWS SDK 2, and has a number of bug fixes, such as supporting retries. Version 2.1.1 was just released to Maven central.

How to add user HTTP Headers in CXF response?

For some reason there is no 'Content-Length' HTTP headers in CXF response.
I've decided to implement interceptor that does it, but unfortunately there is no added header in sent data (intercepted using Wireshark). I can set breakpoint and i can see interceptor is invoked. What's wrong?
/**
* Adds Content-Length header for the outcoming messages
*/
public class AddContentLengthInterceptor extends AbstractLoggingInterceptor {
private static final String CONTENT_LENGTH_ADDED = AddContentLengthInterceptor.class.getName() + ".log-setup";
public AddContentLengthInterceptor(String phase) {
super(phase);
addBefore(StaxOutInterceptor.class.getName());
}
public AddContentLengthInterceptor() {
this(Phase.PRE_PROTOCOL); // before streaming
}
#Override
protected Logger getLogger() {
return null;
}
#Override
public void handleMessage(Message message) throws Fault {
final OutputStream os = message.getContent(OutputStream.class);
final Writer iowriter = message.getContent(Writer.class);
if (os == null && iowriter == null) {
return;
}
// ignore double processing of the message
boolean hasAddedHeader = message.containsKey(CONTENT_LENGTH_ADDED);
if (!hasAddedHeader) {
message.put(CONTENT_LENGTH_ADDED, Boolean.TRUE);
if (os != null) {
// Write the output while caching it for adding header later
final CacheAndWriteOutputStream newOut = new CacheAndWriteOutputStream(os);
message.setContent(OutputStream.class, newOut);
newOut.registerCallback(new LoggingCallback(message, os));
}
}
}
class LoggingCallback implements CachedOutputStreamCallback {
private final Message message;
private final OutputStream origStream;
public LoggingCallback(final Message msg, final OutputStream os) {
this.message = msg;
this.origStream = os;
}
public void onFlush(CachedOutputStream cos) {
}
public void onClose(CachedOutputStream cos) {
long contentLength = cos.size();
Map<String, List<String>> headers = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);
if (headers == null)
headers = new HashMap<String, List<String>>();
headers.put("Content-Length", Arrays.asList(String.valueOf(contentLength)));
message.put(Message.PROTOCOL_HEADERS, headers);
try {
// empty out the cache
cos.lockOutputStream();
cos.resetOut(null, false);
} catch (Exception ex) {
//ignore
}
message.setContent(OutputStream.class, origStream);
}
}
}
This is now server-side endpoints are created:
mediaService = new MediaService(ip, rtspPort, streamUri);
ProviderImpl provider = new ProviderImpl();
mediaEndpoint = (EndpointImpl) provider.createEndpoint(null, mediaService);
String mediaServiceURL = MessageFormat.format("http://{0}:{1}/onvif/media_service", ip, String.valueOf(port));
mediaEndpoint.publish(mediaServiceURL);
// add "Content-Length" header
mediaEndpoint.getServer().getEndpoint().getOutInterceptors().add(contentLengthInterceptor);
I think you are using Transfer-Encoding as chunked. Which results in no content-length header since it is defined that way in RFC.
Messages MUST NOT include both a Content-Length header field and a
non-identity transfer-coding. If the message does include a
non-identity transfer-coding, the Content-Length MUST be ignored.
I am not exactly sure but, either cxf removes your header since it is not allowed or it sets but sub layers remove it. As far as I know cxf uses HttpUrlConnection to transport your message. And that layer sets Content-length header on normal cases. But since you are using chunked transfer, that layer may override it.
In order to fix it you must change your Transfer-Encoding. And since CXF's itself sets content-length header you are no longer need to write your Interceptor.
By the way, if you use a custom header, you will realise that your interceptor works like a charm.

How to send additional fields to soap handler along with soapMessage?

I am logging RequestXML for a webservice client using SoapHandler as follows
public boolean handleMessage(SOAPMessageContext smc) {
logToSystemOut(smc);
return true;
}
private void logToSystemOut(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean)
smc.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
out.println("\nOutbound message:");
} else {
out.println("\nInbound message:");
}
SOAPMessage message = smc.getMessage();
try {
message.writeTo(out);
out.println("");
} catch (Exception e) {
out.println("Exception in handler: " + e);
}
}
Got a new requirenment to add this xml to DB along with some extra values(which are not present in the xml). Is there any way I can pass few additional fields to above soap handler (in handleMessage method)?
Please note that changing the xml/WSDL or adding this to SOAP message header is not an option for me as it is owned by other interface. Any other solution?
Thanks!
You can cast your service class to a class of type "BindingProvider". In this form you can use it to assign it objects which you can access later from your SOAPHandler. Another useful usage is that you also can change the endPoint URL this way.
Before calling the service you do:
MySoapServicePortType service = new MySoapService().getMySoapServicePort();
BindingProvider bp = (BindingProvider)service;
MyTransferObject t = new MyTransferObject();
bp.getRequestContext().put("myTransferObject", t);
TypeResponse response = service.doRequest();
SOAPMessage message = t.getRequestMessage(message);
From your logging function you do:
private void logToSystemOut(SOAPMessageContext smc) {
...
MyTransferObject t = (MyTransferObject) messageContext.get("myTransferObject");
if (outboundProperty.booleanValue())
t.setRequestMessage(message);
else
t.setResponseMessage(message);
...
}

Grails/Groovy - using multiple #TestMixin

In Grails I have a service that I want to unit test. The service uses these imports:
import grails.converters.JSON
import grails.web.JSONBuilder
I want the service to do get data and convert it to JSON:
def tables = DataProfileStats.withCriteria {
projections {
distinct("tableName")
}
};
The helper method I wrote to build the JSON is:
public String buildNodeString(String nodeText)
{
def builder = new JSONBuilder();
JSON result = builder.build {
hello = "world"
};
return result.toString();
}
In the unit test I have to add #TestMixin(ControllerUnitTestMixin) so the JSON adapter is loaded. But I also have to add #TestMixin(DomainClassUnitTestMixin) so I can mock the database object.
Any ideas on how to have multiple #TestMixin or is this a design issue with me having a import grails.web.JSONBuilder in a service class? Otherwise, I have to use a JAVA/JSON library or put the JSON stuff in a controller.
This is what I want the test to look like:
#TestMixin(ControllerUnitTestMixin)
#TestMixin(DomainClassUnitTestMixin)
class JsTreeJSONGeneratorServiceTests {
void testSomething() {
DataProfileStats stats1 = new DataProfileStats();
stats1.tableName = "table";
mockDomain(DataProfileStats, stats1);
JsTreeJSONGeneratorService service = new JsTreeJSONGeneratorService();
String json = service.buildNodeString();
assert json != "";
}
}
I get a #TestMixin(ControllerUnitTestMixin)
#TestMixin(DomainClassUnitTestMixin)
class JsTreeJSONGeneratorServiceTests {
void testSomething() {
DataProfileStats stats1 = new DataProfileStats();
stats1.tableName = "table";
mockDomain(DataProfileStats, stats1);
JsTreeJSONGeneratorService service = new JsTreeJSONGeneratorService();
String json = service.buildNodeString();
assert json != "";
}
}
I get a #TestMixin(ControllerUnitTestMixin)
#TestMixin(DomainClassUnitTestMixin)
class JsTreeJSONGeneratorServiceTests {
void testSomething() {
DataProfileStats stats1 = new DataProfileStats();
stats1.tableName = "table";
mockDomain(DataProfileStats, stats1);
JsTreeJSONGeneratorService service = new JsTreeJSONGeneratorService();
String json = service.buildNodeString();
assert json != "";
}
}
I get a "Cannot specify duplicate annotation on the same member : grails.test.mixin.TestMixin" exception.
Thanks
Found it!
#TestMixin([GrailsUnitTestMixin, ControllerUnitTestMixin, DomainClassUnitTestMixin])
Apparently, this is due to a Grails bug. The problem with mixing in the ControllerUnitTextMixin, is that it also does (and/or potentially will do) a lot of logic unrelated or unhelpful to services, and is essentially a workaround rather than a fix. Scott's answer is definitely sparse and clean in the sense that no other changes are made, but given some of the lack of backwards compatibility with Grails 2.0, I would be concerned with future releases that may, say, force logic into the setUp() method that may break for services.
So for completeness, I am including another potential workaround taken directly from the JIRA, all credit to Ellery Crane:
package util.converters
import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder
import org.codehaus.groovy.grails.web.converters.configuration.ConverterConfiguration
import org.codehaus.groovy.grails.web.converters.configuration.DefaultConverterConfiguration
import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller
import org.codehaus.groovy.grails.web.converters.Converter
import org.codehaus.groovy.grails.web.converters.configuration.ChainedConverterConfiguration
class JSON extends grails.converters.JSON{
public JSON(Object target) {
super(target)
}
#Override
protected ConverterConfiguration<grails.converters.JSON> initConfig() {
ConverterConfiguration config = super.initConfig()
if(config.getOrderedObjectMarshallers().size() == 0){
initDefaultMarshallers()
config = super.initConfig()
}
return config
}
private void initDefaultMarshallers(){
List<ObjectMarshaller<grails.converters.JSON>> marshallers = new ArrayList<ObjectMarshaller<grails.converters.JSON>>();
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.ArrayMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.ByteArrayMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.CollectionMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.MapMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.EnumMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.ProxyUnwrappingMarshaller<grails.converters.JSON>());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.DateMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.ToStringBeanMarshaller());
boolean includeDomainVersion = true;
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller(includeDomainVersion));
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.GroovyBeanMarshaller());
marshallers.add(new org.codehaus.groovy.grails.web.converters.marshaller.json.GenericJavaBeanMarshaller());
DefaultConverterConfiguration<grails.converters.JSON> cfg = new DefaultConverterConfiguration<grails.converters.JSON>(marshallers);
cfg.setEncoding("UTF-8");
cfg.setCircularReferenceBehaviour(Converter.CircularReferenceBehaviour.DEFAULT)
cfg.setPrettyPrint(false);
ConvertersConfigurationHolder.setDefaultConfiguration(grails.converters.JSON.class, new ChainedConverterConfiguration<grails.converters.JSON>(cfg));
}
}
then
Just import util.converters.JSON instead of grails.converters.JSON, and everything else works seamlessly.

Webservice Namespace problem

I have a ASP.NET webservice which is called by a Java client. The client sends a SOAP message as input but the problem is that it never reaches my webservice method. I always get null input. I used TraceListener and saw this warning which may cause the problem:
The element was not expected in this context: ... Expected elements: http://client.ns.url/:ListenerInput.
This is what the client sends:
<?xml version="1.0" encoding="utf-8"?>
<S:Envelope xmlns:S = "http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:Listener xmlns:ns2 = "http://client.ns.url/">
<ListenerInput>
<errorCode>0</errorCode>
<errorDescription>Success</errorDescription>
<firstPrice xsi:nil = "true" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"/>
</ListenerInput>
</ns2:Listener>
</S:Body>
</S:Envelope>
And here's my webmethod:
[System.Web.Services.WebServiceAttribute(Namespace = "http://client.ns.url/")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.42")]
[System.Web.Services.WebServiceBindingAttribute(Name = "ListenerWebServicePortSoapBinding", Namespace = "http://client.ns.url/")]
public partial class ListenerService : System.Web.Services.WebService
{
[System.Web.Services.WebMethodAttribute()]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://client.different.url/services/action/ListenerService/Listener", RequestElementName = "Listener", RequestNamespace = "http://client.ns.url/", ResponseNamespace = "http://client.ns.url/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlElementAttribute("return")]
public ListenerResponse Listener([System.Xml.Serialization.XmlElementAttribute("ListenerInput")]ListenerInput listenerInput)
{
..
This is the input class:
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://client.ns.url")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class ListenerInput
{
public int errorCode;
public string errorDescription;
public float? firstPrice;
}
What should I do to solve this? Here's the some of the trace log:
Calling IHttpHandler.ProcessRequest
Caller: System.Web.Services.Protocols.SyncSessionlessHandler#47754503::ProcessRequest()
Request Host Address: xxx
Request Url: [POST] http:/my.website/Listener.asmx
..
Calling XmlSerializer [Read Request]
Method: Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer#53218107::Deserialize(System.Web.Services.Protocols.SoapServerProtocol+SoapEnvelopeReader#4153573=.., (null))
Caller: System.Web.Services.Protocols.SoapServerProtocol#51389704::ReadParameters()
...
The element was not expected in this context: <ListenerInput>..</ListenerInput>. Expected elements: http://client.ns.url/:ListenerInput.
...
Return from XmlSerializer [Read Request]
Caller: System.Web.Services.Protocols.SoapServerProtocol#51389704::ReadParameters()
...
Calling ListenerResponse Listener(ListenerInput)
Method: ListenerService#64693151::Listener((null))
Caller: System.Web.Services.Protocols.SyncSessionlessHandler#47754503::Invoke()
Solved the problem. I was using client's namespace address when I should be using my own. Changed this:
[System.Web.Services.WebServiceBindingAttribute(Name = "ListenerWebServicePortSoapBinding", Namespace = "http://client.ns.url/")]
public partial class ListenerService : System.Web.Services.WebService
to this:
[System.Web.Services.WebServiceBindingAttribute(Name = "ListenerWebServicePortSoapBinding", Namespace = "http://my.ns.url/")]
public partial class ListenerService : System.Web.Services.WebService