How do you throw a custom soap fault on a JAX-WS web service? How can I specify the faultCode, faultString and detail of the soap fault? Is it possible to set the value of the detail as bean instead of a String?
Please note that I'm developing using code-first approach.
Use the #WebFault annotation.
You can see a good example in Using SOAP Faults and Exceptions in Java JAX-WS Web Services - Eben Hewitt on Java.
You will see the example:
#WebFault(name="CheckVerifyFault",
targetNamespace="http://www.example.com")
public class CheckVerifyFault extends Exception {
/**
* Java type that goes as soapenv:Fault detail element.
*/
private CheckFaultBean faultInfo;
public CheckVerifyFault(String message, CheckFaultBean faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public CheckVerifyFault(String message, CheckFaultBean faultInfo,
Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public CheckFaultBean getFaultInfo() {
return faultInfo;
}
}
UPDATE
Another way is to declare the typical exception in the throws clause.
e.g. Suppose the following is my exception class:
package pkg.ex;
public class FooException extends Exception {
public FooException(String message, Throwable cause) {
super(message, cause);
}
}
And the next class is the service implementation.
package pkg.ws;
import javax.jws.WebService;
import pkg.ex.FooException;
#WebService(serviceName = "FooSvc")
public class FooService {
public String sayHello(String name) throws FooException {
if (name.isEmpty()) {
Throwable t = new IllegalArgumentException("Empty name");
throw new FooException("There is one error", t);
}
return "Hello, " + name;
}
}
If my request is:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.pkg/">
<soapenv:Header/>
<soapenv:Body>
<ws:sayHello>
<arg0>Peter</arg0>
</ws:sayHello>
</soapenv:Body>
</soapenv:Envelope>
There is no problem:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:sayHelloResponse xmlns:ns2="http://ws.pkg/">
<return>Hello, Peter</return>
</ns2:sayHelloResponse>
</S:Body>
</S:Envelope>
But...
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.pkg/">
<soapenv:Header/>
<soapenv:Body>
<ws:sayHello>
<arg0></arg0>
</ws:sayHello>
</soapenv:Body>
</soapenv:Envelope>
Then...
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
<faultcode>S:Server</faultcode>
<faultstring>There is one error</faultstring>
<detail>
<ns2:FooException xmlns:ns2="http://ws.pkg/">
<message>There is one error</message>
</ns2:FooException>
</detail>
</S:Fault>
</S:Body>
</S:Envelope>
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPFault;
import javax.xml.ws.soap.SOAPFaultException;
import javax.xml.namespace.QName;
...
SOAPFactory soapFactory = SOAPFactory.newInstance();
SOAPFault soapFault = soapFactory.createFault(
"Your custom message",
new QName("http://schemas.xmlsoap.org/soap/envelope/", "Client"));
throw new SOAPFaultException(soapFault);
To choose the right fault code, see http://www.tutorialspoint.com/soap/soap_fault.htm .
Related
In my JAX-WS web service, I need to customize my SOAP Response Envelope.
At the moment, it is like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<methodResponse xmlns="http://interfaces.webservice.ucmdb.com">
<methodReturn>202</methodReturn>
</methodResponse >
</soapenv:Body>
</soapenv:Envelope>
And i need it to look like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<code>123</code>
</soapenv:Header>
<soapenv:Body>
<getCIResponse xmlns="http://interfaces.webservice.ucmdb.com">
<getCIReturn>202</getCIReturn>
</getCIResponse>
</soapenv:Body>
</soapenv:Envelope>
So, how does one write the <soapenv:Header> part in a JAX-WS soap envelope?
Here is my WebMethod implementation (quite simple at the moment):
#WebMethod
public int operation(#WebParam(name="username", header=false)String id) {
return 202;
}
1.) In order to add a header to your SOAP message in JAX-WS you can implement the SOAPHandler interface. This will give you access to the SOAPMessage and you can use the SAAJ api to add/update the SOAP Header. Keep in mind this handler is bi-directional.
public class Handler1 implements SOAPHandler<SOAPMessageContext>{
#Override
public boolean handleMessage(SOAPMessageContext context) {
boolean isOutbound = (boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
if(isOutbound){
SOAPMessage msg = context.getMessage();
try {
//Grab the header
SOAPHeader header = msg.getSOAPHeader();
//Add whatever QName you need
header.addHeaderElement(new QName("code"));
//Save changes.
msg.saveChanges();
} catch (SOAPException e) {
e.printStackTrace();
}
}
//True continue, false halt.
return true;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
return false;
}
#Override
public void close(MessageContext context) {
}
#Override
public Set<QName> getHeaders() {
//Can also add a QName here.
return null;
}
}
2.) In order to register the Handler you need to add the handler via an xml handler configuration file that should be kept in the classpath.
<?xml version="1.0" encoding="UTF-8"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>com.calvinmmiller.service.Handler1</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
3.) This file can be added to the SEI(Service Endpoint Interface) with the annotation below:
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
#WebService
#HandlerChain( file = "handlerFile.xml")
public class Service {
public int operation(String id){
return 202;
}
public static void main(String[] args){
Endpoint.publish("http://localhost:8080/soap", new Service());
}
}
I'm using Axis2-1.6.1 and have been able to successfully send a SOAP request. Here's an example of the request:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="true">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>***username***</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">***pass***</wsse:Password>
<wsse:Nonce Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">***nonce***</wsse:Nonce>
<wsu:Created Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">***datetime***</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
<wsa:Action xmlns:wsa="http://www.w3.org/2005/08/addressing">http://mysite/contract/users/v3/IUsers/EchoAuthenticated</wsa:Action>
</soapenv:Header>
<soapenv:Body>
<ns6:EchoAuthenticated xmlns:ns6="http://mysite/contract/users/v3">
<ns6:value>success</ns6:value>
</ns6:EchoAuthenticated>
</soapenv:Body>
</soapenv:Envelope>
Upon receiving the response, this exception is thrown:
org.apache.axis2.AxisFault: Must Understand check failed for header http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd : Security
I'm under the impression, after doing some research, that there's something in the response that Axis2 doesn't like. Puzzled, I copied the above request and pasted it into SoapUI and fired it. It works, I receive the below response. I also confirmed, using Fiddler, that this is the same response I am getting when I send this request in Eclipse its just there's something about it that Axis2 doesn't like client-side, perhaps the mustUnderstand?
Here's the response:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1">http://mysite/contract/users/v3/IUsers/EchoAuthenticatedResponse</a:Action>
<a:RelatesTo>urn:uuid:***guid***</a:RelatesTo>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>***datetime***</u:Created>
<u:Expires>***datetime***</u:Expires>
</u:Timestamp>
</o:Security>
</s:Header>
<s:Body>
<EchoAuthenticatedResponse xmlns="http://mysite/contract/users/v3">
<EchoAuthenticatedResult>This is the Users service answering back. The value you sent was: success</EchoAuthenticatedResult>
</EchoAuthenticatedResponse>
</s:Body>
</s:Envelope>
I'm limited in my ability to move to a more recent version of Axis2 as this is bundled with a product but I need to find out how I can get passed this error.
I found one solution which is to set mustUnderstand instances in the response to false
To accomplish this I've done the following:
Create a Handler class which extends *org.apache.axis2.handlers.AbstractHandler
MustUnderstandHandler.java
import java.util.Iterator;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPHeader;
import org.apache.axiom.soap.SOAPHeaderBlock;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
public class MustUnderstandHandler extends org.apache.axis2.handlers.AbstractHandler {
#Override
public InvocationResponse invoke(MessageContext messageContext) throws AxisFault {
try{
System.out.println("RemoveMustUnderstandAll: invoke " + messageContext);
SOAPEnvelope env = messageContext.getEnvelope();
SOAPHeader header = env.getHeader();
if(header != null){
for(Iterator<?> itr = header.getChildElements(); itr.hasNext();){
SOAPHeaderBlock headerBlock = (SOAPHeaderBlock) itr.next();
if(headerBlock.getMustUnderstand()){
headerBlock.setMustUnderstand(false);
System.out.println("RemoveMustUnderstandAll (" + messageContext + "): setMustUnderstand(false) to " + headerBlock.getQName());
}
}
}
}
catch(Exception e){
System.out.println(e.toString());
}
return InvocationResponse.CONTINUE;
}
}
Wire the AxisConfiguration to use the handler class
In the generated Stub (created from WSDL2Java) I've located instances where it's executing the client and before each of these lines I included the following:
MyStub.java
AxisConfiguration axisConfiguration = _messageContext.getConfigurationContext().getAxisConfiguration();
ArrayList arrayList = new ArrayList();
arrayList.add(new MustUnderstandHandler());
axisConfiguration.setInPhasesUptoAndIncludingPostDispatch(arrayList);
// execute the operation client
_operationClient.execute(true);
We currently have a problem with CXF 2.7.3 on Jboss EAP 6.2 with a custom SoapFault exception.
The Subcode and its value is not displayed when we send a custom SoapFault:
Here is what we want from cxf:
<?xml version="1.0" encoding="UTF-8"?>
<tns:Fault
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://www.w3.org/2003/05/soap-envelope">
<tns:Code>
<tns:Value>tns:Sender</tns:Value>
<tns:Subcode>
<tns:Value>value</tns:Value>
</tns:Subcode>
</tns:Code>
<tns:Reason>
<tns:Text xml:lang="fr">
****
</tns:Text>
</tns:Reason>
<tns:Detail>
**Custom fault***
</tns:Detail>
</tns:Fault>
Here is what we have so far:
<?xml version="1.0" encoding="UTF-8"?>
<tns:Fault
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://www.w3.org/2003/05/soap-envelope">
<tns:Code>
<tns:Value>tns:Sender</tns:Value>
</tns:Code>
<tns:Reason>
<tns:Text xml:lang="fr">
****
</tns:Text>
</tns:Reason>
<tns:Detail>
**Custom fault***
</tns:Detail>
</tns:Fault>
The subcode is completely missing.
We tried to use a custom interceptor (extending from LoggingOutInterceptor or AbstractInterceptor) from CXF like this to intercept the custom fault:
public class SoapRequestInterceptor extends LoggingOutInterceptor {
private static Logger log = Logger.getLogger(SoapRequestInterceptor.class);
public SoapRequestInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage message) throws Fault{
SoapMessage soapMessage = message.getContent(SoapMessage.class);
if (soapMessage != null) {
log.info( "request intercepted:" + soapMessage.toString());
}
}
}
The interceptor is not even called when we add him either to CXF bus or to jaxws interceptor (it’s added at the start of the application though since it gets through the constructor).
How can we intercept a custom soap Fault message and edit it in CXF?
Thanks a lot!
As asked here's the way we declare the interceptor in spring applicationContext.xml :
<cxf:bus>
<cxf:outFaultInterceptors>
<ref bean="soapRequestInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>
<bean id="soapRequestInterceptor" class="fr.test.SoapRequestInterceptor" />
<jaxws:server serviceClass="fr.test.PriseEnChargeB2ServiceSP"
address="" serviceBean="#service">
<jaxws:binding>
<soap:soapBinding version="1.2" mtomEnabled="true" />
</jaxws:binding>
</jaxws:server>
Note : the interceptor is well instancied, but not called after a soap fault throw from our WS
The exception thrown at the end of our WS is this one :
public class PecSoapFaultException extends SoapFault {
private static final long serialVersionUID = 1L;
public TypeErreur erreur;
public PecSoapFaultException(String message, TypeErreur structure) {
super(message, new QName(""));
this.erreur = structure;
}
public PecSoapFaultException(String message, TypeErreur structure, QName faultcode) {
super(message, faultcode);
this.erreur = structure;
}
public PecSoapFaultException(String message, TypeErreur structure, QName faultcode,
QName subcode) {
super(message, faultcode);
this.setSubCode(subcode);
this.erreur = structure;
}
public TypeErreur getFaultInfo() {
return erreur;
}
The problem that your interceptor is not called is that you do not override the correct method. You should have your code like this:
#Override
public void handleMessage(Message message) throws Fault {
SoapMessage soapMessage = message.getContent(SoapMessage.class);
if (soapMessage != null) {
log.info( "request intercepted:" + soapMessage.toString());
}
}
Then your interceptor will be called. But as I said in my comment: in case of a fault the soapmessage is null. So you won't get any output in your log.
Our system consumes SOAP Web Service, using JAX-WS client stubs generated based on service's WSDL. In case of error server returns SOAP faults like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<s:Fault>
<faultcode>SomeErrorCode</faultcode>
<faultstring xml:lang="en-US">Some error message</faultstring>
<detail>
<ApiFault xmlns="http://somenamespace.com/v1.0" xmlns:a="http://somenamespace.com/v1.0" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:RequestId>123456789</a:RequestId>
<a:CanRetry>true</a:CanRetry>
</ApiFault>
</detail>
</s:Fault>
</s:Body>
Based on WSDL SomeCustomFault exception class is generated and all service methods are declared to throw this (see below) exception.
#WebFault(name = "ApiFault", targetNamespace = "http://services.altasoft.ge/orders/v1.0")
public class SomeCustomFault
extends Exception
{
private ApiFault faultInfo;
public SomeCustomFault(String message, ApiFault faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public SomeCustomFault(String message, ApiFault faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public ApiFault getFaultInfo() {
return faultInfo;
}
}
As you can see this custom fault exception extends Exception and not SOAPFaultException. Hovewer I need to get SOAP fault's faultcode which could be retrieved only from SOAPFaultException using getFaultCode method. Could you tell me how can I reach SOAPFaultException or SOAP fault's faultcode in place where I catch above mentioned custom fault exception?
You could implement a JAX-WS handler and add it to your client web service reference. This will be given opportunity to handle the request message and response message OR notified of a fault.
Create a SOAPHandler<SOAPMessageContext> and your handleFault() method will be passed the SOAPMessageContext. From that you can getMessage().getSOAPBody().getFault() to get the SOAPFault, which contains getFaultCode() and getDetail().
Assign your new fault handler to your web service ref. One way is via #HandlerChain. It will be invoked prior to your catch clause.
I'm trying to create a generic web service that will always respond with "OK", regardless of the request's header or body contents. I can do this in Axis2 with a RawXMLInOutMessageReceiver, but I'd prefer to use JAX-WS (which I am completely new to) if at all possible. So far I've got a simple interface:
#WebService
public interface DummyService {
#WebMethod String processMessage(Object obj);
}
and a simple implementaion:
#WebService(endpointInterface = "com.dummyservice.DummyService")
public class DummyServiceImpl implements DummyService {
#Override
public String processMessage(Object obj) {
return "OK";
}
}
I can successfully publish the service with javax.xml.ws.Endpoint#publish(...), but when I hit it with a simple SOAP request, e.g.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<derp/>
</soapenv:Body>
</soapenv:Envelope>
I'm greeted with a SOAPFault stating Cannot find dispatch method for {}derp.
Is it even possible to create a generic/dumb web service that will ACK everything with JAX-WS? If so, could someone point me in the right direction?
EDIT
Thanks to the tip from McDowell, I was able to do this with a SOAPHandler:
public class DummySOAPHandler implements SOAPHandler {
#Override
public boolean handleMessage(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public boolean handleFault(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public void close(MessageContext context) { }
#Override
public Set<QName> getHeaders() {
return null;
}
private boolean process(SOAPMessageContext ctx) {
try {
SOAPMessage message = ctx.getMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPBody body = message.getSOAPBody();
if ((Boolean) ctx.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {
Iterator<SOAPElement> bodyChildren = body.getChildElements();
while (bodyChildren.hasNext()) {
SOAPElement child = bodyChildren.next();
child.detachNode();
}
body.addBodyElement(envelope.createName("OK"));
message.saveChanges();
}
} catch (SOAPException e) {
e.printStackTrace();
}
return true;
}
}
I expect your service is expecting something of the form:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dum="http://yournamespace/">
<soapenv:Header/>
<soapenv:Body>
<dum:processMessage>
<!-- xsd:anyType -->
</dum:processMessage>
</soapenv:Body>
</soapenv:Envelope>
Add ?WSDL to your endpoint and inspect the operation input XML type and the namespaces.
You might be able to do something with a logical handler (javadoc) to transform the incoming request to this form - I haven't tried.