I'm implementing a web service for quite an old (but sadly unchangeable) interface. I have an issue where the client that is calling my service expects a certain namespace in the SOAP response and I'm having difficulty in changing it to match.
Considering a hello world example, I want this:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:helloResponse xmlns:ns2="http://test/">
<return>Hello Catchwa!</return>
</ns2:helloResponse>
</S:Body>
</S:Envelope>
to look like this:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<customns:helloResponse xmlns:customns="http://test/">
<return>Hello Catchwa!</return>
</customns:helloResponse>
</S:Body>
</S:Envelope>
I found something similar to what I'm trying to do here but I'm having trouble getting similar code to execute properly. (I'd like to stick with Metro and not have to change to cxf or axis)
My implementation of JAXBContextFactory
that returns a JAXBRIContext
looks like this:
import com.sun.xml.bind.api.JAXBRIContext;
import com.sun.xml.bind.api.TypeReference;
import com.sun.xml.ws.api.model.SEIModel;
import com.sun.xml.ws.developer.JAXBContextFactory;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
public class HelloJaxbContext implements JAXBContextFactory
{
@Override
public JAXBRIContext createJAXBContext(SEIModel seim, List<Class> classesToBind, List<TypeReference> typeReferences) throws JAXBException {
List<Class> classList = new ArrayList<Class>();
classList.addAll(classesToBind);
List<TypeReference> refList = new ArrayList<TypeReference>();
for (TypeReference tr : typeReferences) {
refList.add(new TypeReference(new QName(tr.tagName.getNamespaceURI(), tr.tagName.getLocalPart(), "customns"), tr.type, tr.annotations));
}
return JAXBRIContext.newInstance(classList.toArray(new Class[classList.size()]), refList, null, seim.getTargetNamespace(), false, null);
}
}
Some test code for the web service is simply:
import com.sun.xml.ws.developer.UsesJAXBContext;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
@WebService(serviceName = "Hello")
@UsesJAXBContext(value = HelloJaxbContext.class)
public class Hello
{
@WebMethod(operationName = "hello")
public String hello(@WebParam(name = "name") String txt)
{
return "Hello " + txt + "!";
}
}
In Tomcat 7.0.32 and Glassfish 3.1.2 using jaxws-rt 2.2.7 (from Maven), the above code doesn't affect my web service output (the namespace prefix is still "ns2").
SOAP messages must declare the namespace of the Envelope element to be the standard SOAP 1.1 envelope namespace, "http://schemas.xmlsoap.org/soap/envelope/". If a SOAP application receives a message based on some other namespace, it must generate a fault.
JAX-WS is a technology for building web services and clients that communicate using XML. JAX-WS allows developers to write message-oriented as well as RPC-oriented web services. In JAX-WS, a web service operation invocation is represented by an XML-based protocol such as SOAP.
The Contract-first approach tells us to create first WSDL and then create end-point interface and implementation class. The Contract-last approach tells us to create first end-point interface and implementation class then create WSDL file.
JAX-WS RI 2.3. 1 is a Web Services framework that provides tools and infrastructure to develop Web Services solutions for the end users and middleware developers. With JAX-WS RI 2.3. 1, clients and web services have a big advantage: the platform independence of the Java programming language.
If you started from the WSDL of the old service and generated all the various JAXB annotated request and response wrapper classes using wsimport
, then in the generated package you should find a package-info.java
such as
@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/")
package com.example.test;
JAXB provides a mechanism for you to suggest prefix mappings on the @XmlSchema
annotation, so you could try modifying package-info.java
to read
@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/",
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "customns",
namespaceURI="http://test/")
}
)
package com.example.test;
and see if that makes any difference to the generated messages. This also has the advantage of being pure JAXB spec (i.e. not dependent on the RI-specific custom context factory).
If you need to re-run wsimport
you can prevent it from overwriting your modified package-info
by passing the -npa
option to xjc
(this tells it not to generate a package-info.java
but instead to put all the necessary namespace
settings on the class-level annotations instead). Exactly how you do this depends how you're running wsimport
.
command line:
wsimport -B-npa ....
Ant:
<wsimport wsdl="..." destdir="..." .... >
<xjcarg value="-npa" />
</wsimport>
Maven:
<plugin>
<groupId>org.jvnet.jax-ws-commons</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<xjcArgs>
<xjcArg>-npa</xjcArg>
</xjcArgs>
</configuration>
</execution>
</executions>
</plugin>
The recommended/standard way to achieve what you're trying to achieve is with a SOAPMessage Handler. They're analogous to java web application filters (which in theory could also work here) as they are used to implement the Chain of Responsibility pattern. For example, in your case, you can have this :
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class SOAPBodyHandler implements SOAPHandler<SOAPMessageContext> {
static final String DESIRED_NS_PREFIX = "customns";
static final String DESIRED_NS_URI = "http://test/";
static final String UNWANTED_NS_PREFIX = "ns";
@Override
public Set<QName> getHeaders() {
//do nothing
return null;
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
if ((boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { //Check here that the message being intercepted is an outbound message from your service, otherwise ignore.
try {
SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //get the SOAP Message envelope
SOAPBody body = msg.getBody();
body.removeNamespaceDeclaration(UNWANTED_NS_PREFIX);
body.addNamespaceDeclaration(DESIRED_NS_PREFIX, DESIRED_NS_URI);
} catch (SOAPException ex) {
Logger.getLogger(SOAPBodyHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
return true; //indicates to the context to proceed with (normal)message processing
}
@Override
public boolean handleFault(SOAPMessageContext context) {
//do nothing
return null;
}
@Override
public void close(MessageContext context) {
//do nothing
}
}
On your Service Implementation Bean class declaration, add
@HandlerChain(file = "handler-chain.xml")
The annotation above is a reference to the configuration file that actually allows your handler to kick-in. The config file will look something like this
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>your.handler.FQN.here</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
Try this at home. This specific code has NOT been tested
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With