i am working on web services project with Apache CXF.
I wan't to handle exception and customize response:
public class FaultInterceptor extends
AbstractSoapInterceptor {
public FaultInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage soapMessage) throws Fault {
Fault fault = (Fault) soapMessage.getContent(Exception.class);
QName faultCode = new QName("11111");
fault.setFaultCode(faultCode);
So here is what i get in the response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:11111</faultcode>
<faultstring>Message</faultstring>
</soap:Fault>
</soap:Body>
How can i remove the text "soap:" and let only 11111?
Please help me and thanks in advance
To customize the SOAP response, you can implement either one of these:
JAX-WS handler;
CXF interceptor (extend AbstractSoapInterceptor);
Servlet Filter.
--EDIT 2019-02-20--
According to Javadoc (and SOAP spec), the faultcode must be in the form "prefix:localname" where "prefix" is the prefix of a declared XML namespace in your XML, or you can have no prefix - that you want - if it is declared as the default namespace, e.g. with xmlns="my-custom-faultcode-namespace-uri" somewhere, e.g. in the soap:Envelope element. So one way - not sure it's the easiest but it is SOAP standard compliant - consists to:
1) Make up your own custom namespace for this faultcode
2) Try changing the QName with empty string as namespace prefix:
QName faultCode = new QName("my-custom-faultcode-namespace-uri", "11111", "");
If this is not enough (I would be surprised it is that simple), you may have force CXF to use your custom namespace as the default (without prefix). According to this post, to customize namespaces and prefixes on the soap enveloppe in CXF, you change the Map in the jaxws 'soap.env.ns.map' property.
I was looking for the exactly same thing since migrating an old system to behave exactly the same.
I came up with the following Solution.
class SoapFaultEndpointInterceptor extends EndpointInterceptorAdapter
{
private static final Pattern SOAP_CODE_FAULT_SPLITTER = Pattern.compile(":");
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception
{
SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
modifySoapFaultCode(soapResponse);
return super.handleFault(messageContext, endpoint);
}
private void modifySoapFaultCode(SaajSoapMessage soapResponse)
{
try {
SOAPMessage soapMessage = soapResponse.getSaajMessage();
SOAPBody body = soapMessage.getSOAPBody();
SOAPFault soapFault = body.getFault();
modifyFaultCodeIfPresent(soapFault);
} catch (SOAPException e) {
throw new SoapModifiyingException("Modifying faultcode did not work properly.", e);
}
}
private void modifyFaultCodeIfPresent(SOAPFault fault)
{
if (fault != null) {
String newFaultCode = cleanFaultCode(fault.getFaultCode());
fault.setFaultCode(newFaultCode);
}
}
private String cleanFaultCode(String oldFaultCode)
{
String[] cleanFaultCode = SOAP_CODE_FAULT_SPLITTER.split(oldFaultCode);
Assert.isTrue(cleanFaultCode.length == 2, "No proper faultcode provided!");
return cleanFaultCode[1].trim();
}
And by adding SoapFaultEndpointInterceptor to your Interceptor, it should work.
#EnableWs
#Configuration
public class SoapServerConfig extends WsConfigurerAdapter
{
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors)
{
interceptors.add(new SoapFaultEndpointInterceptor());
}
}
Related
Trying to handle an empty SOAP message with Spring Web Services but failing.
So, I have a request to provide an endpoint for a sort of a PING method. Basically the SOAP messages I can handle look like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:def="http://www.something.com/edf/services/defaultservice">
<soapenv:Header/>
<soapenv:Body>
<def:ServiceReqType>
<transactionId>1111</transactionId>
<subscriberId>2222</subscriberId>
</def:ServiceReqType>
</soapenv:Body>
</soapenv:Envelope>
and that I can handle with an endpoint that is handling ServiceReqType.
But the PING looks like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body/>
</soapenv:Envelope>
That I can't handle, Spring logs a Can't handle [SaajSoapMessage].
What I need to return is the exactly same message.
I understand there that there is a type/class missing that I would provide to #PayloadRoot.
So I am wondering what would an endpoint specification be for this empty bodied request?
Just for reference here is my endpoint for handling the ServiceReqType:
#PayloadRoot(namespace = NAMESPACE_EDF, localPart = "ServiceReqType")
#ResponsePayload
public ServiceRespType serviceResponse(#RequestPayload ServiceReqType request) {
LOGGER.debug("-----> ServiceReqType:{}", request);
return reqProcessor.process(request);
}
UPDATE 1:
So I tried with implementing the interceptor in the following way:
public class CustomEndpointInterceptor implements EndpointInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomEndpointInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("---> Message context: {}", messageContext.toString());
LOGGER.info("---> Message endpoint: {}", endpoint.toString());
return false;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleResponse");
return false;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleFault");
return false;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
LOGGER.info("afterCompletion");
}
}
and then in the WebServiceConfiguration class I added this:
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new CustomEndpointInterceptor());
super.addInterceptors(interceptors);
}
But it still does not work. What I get is that the interceptor is called when SOAP message has a BODY but when it is sent a Ping that is BODYless then I get again the same error message and interceptor is not called. So it seems I must find an interceptor that is further up the chain...
UPDATE 2:
Here is how WSDL file looks like for this Ping...
<message name="PingRequest"/>
<message name="PingResponse"/>
There is nothing as a part inside of these...
For comparison this is how ServiceReqType looks:
<message name="ServiceReqType">
<part name="body" element="tns:ServiceReqTypeDefinition"/>
</message>
and then the ServiceReqTypeDefinition is defined in an accompanying xsd file.
UPDATE 3:
So, found the reason why interceptors won't work on this type of message :-/
Below code is from MessageDispatcher line 234.
// No triggering of interceptors if no endpoint is found
if (endpointNotFoundLogger.isWarnEnabled()) {
endpointNotFoundLogger.warn("No endpoint mapping found for [" + messageContext.getRequest() + "]");
}
throw ex;
So it's on purpose, now I need to find how to handle these incomplete or incorrect SOAP messages... any ideas because rewriting Dispatcher doesn't feel like the right way.
UPDATE 4
So, I managed to intercept the payload, the one that is invalid, by extending AbstractEndpointMapping class and then overriding getInternalEndpoint gives me the possibility to evaluate the message and see if it is this empty Ping request I have been trying to process.
I think this would all be easily solved if I knew how to define a defaultEndpoint because I see that in case SOAP message is not recognised, so no endpoint mapping is found, this defaultEndpoint is used to handle that message.
I noticed that when working with XML specification of Beans then there is a property to define the endpoint as a default one, but how to do it when using annotations?
Another way is to just create an endpoint and then return it from getInternalEndpoint so spring can handle processing, I guess, but I don't know yet how to create an endpoint object... working on that now.
p.s. This documentation mentions defaultEndpoint but not how to set it up in a non XML defining Bean way.
So, in the end I had to create a custom exception handler. Something like this:
public class CustomEndpointNotFoundException extends Exception {
public CustomEndpointNotFoundException(String message) {
super(message);
}
}
then:
#Component
#Order(Ordered.LOWEST_PRECEDENCE)
public class NoEndpointFoundEndpointMapping implements EndpointMapping {
#Override
public EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception {
throw new CustomEndpointNotFoundException("");
}
}
and finally:
#Component
public class CustomEndpointExceptionResolver implements EndpointExceptionResolver {
#Override
public boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) {
if (messageIsPing()) {
return true;
}
return false;
}
}
So basically I am handling it as an error. It's not really ideal solution IMHO, I will still see if I can intercept the endpoint resolution, or define a default endpoint.
I am receiving an xml post request from my vendor having a declaration of this format.
<?xml version=\"1.0\" encoding=\"utf-8\"?>
With this type of xml declarion (I am using Spring MVC with JAXB) I am getting the HTTP Status 400 error which states that "The request sent by the client was syntactically incorrect." I tried to post the same request to my site using postman and i get the very same error.
But on changing the xml declarion by removing all the backslashes( see below)
<?xml version="1.0" encoding="utf-8"?>
the error vanishes and i get the correct response with HTTP Status 200, Ok.
My question is how can i intercept this request and modify the xml declaration by removing the forward slashes (My vendor does not comply with modify this from their end).
Below is the sample of my controller
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST}, value ="/listeningurl", consumes = "application/xml", produces = "application/xml")
public ResponseObject lodgementNotifications(#RequestBody RequesObject reqObject)
{
//do stuffs with reqObject;
// Initialize ResponseObject
return responseObject
}
Thanks for the help.
You can extends the HandlerInterceptorAdapter which is :
Abstract adapter class for the AsyncHandlerInterceptor interface, for
simplified implementation of pre-only/post-only interceptors.
#Component
public class MyHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// then access your request body (xml content) to update it
// see link bellow for how to retrieve an xml content from the HttpServletRequest
return super.preHandle(request, response, handler);
}
}
After that you override the addInteceptors of WebMvcConfigurerAdapter by creating a custom class that extends from WebMvcConfigurerAdapter :
#Configuration
public class CustomWebMvc extends WebMvcConfigurerAdapter {
#Autowired
MyHandlerInterceptor myHandlerInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myHandlerInterceptor);
super.addInterceptors(registry);
}
}
To know how to retrieve the xml content from your HttpServletRequest read this Get Posted XML from HttpServletRequest Object
Edit
If your goal is simply to retrieve the http body ( xml content ) and then do what ever you want with it inside your controller you can simply inject an InputStream or a Reader in the #RequestMapping handler method (which is your controller method) like so :
#PostMapping(value = "/topics/xml", consumes = "application/xml")
public void getXmlRequest(InputStream data) throws IOException {
String requestBody = IOUtils.toString(data);
// format the xml and do something with it
}
Spring web doc : Handler Methods :
#RequestMapping handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values.
I was able to resolve the issue. I had to receive the xml request in String format, removed the backslashes then unmarshalled it into its corresponding object. Thanks to #Harry Coder for the hints. Here is the solution that worked for me.
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST}, value ="/listeningurl", consumes = "application/xml", produces = "application/xml")
public ResponseObject lodgementNotifications(#RequestBody String reqObjectXMLStr)
{
//Replace the backslashes from the xml string
String cleanReqObjectXMLStr = reqObjectXMLStr.replaceAll("\\\\", "");
//Unmarshal the string into the corresponding object using JAXB library
RequestObject reqObject = JAXB.unmarshal(new StringReader(cleanReqObjectXMLStr), RequestObject.class);
//do stuffs with reqObject;
// Initialize and set ResponseObject
return responseObject
}
What's my goal?
I'm rather new to Spring WS, I got a WSDL (and along some XSDs, ofcourse) and i want to add some custom header elements to the SOAP response.
I've been searching the web, tried various code pieces, but it's all without any luck... nothing seems to work properly .
What's the problem?
The response SOAP message has a body what spring calls a Payload and my SOAP client (SOAPUI) receives the response rather well.
But here it comes: how should I add new (custom) SOAP headers to the response message?
What's the response xml expected?
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<aud:HeaderInfo xmlns:bd="http://www.myws.com/">
<bd:ID>123</bd:ID>
<bd:Type>text</bd:Type>
</aud:HeaderInfo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ne:myWS xmlns:ne="http://www.iways.com/">
<ne:INFO>
<ne:NAME>JOHN</ne:NAME>
<ne:DESIGNATION>ITA</ne:DESIGNATION>
<ne:MOBILE>9841011113</ne:MOBILE>
</ne:INFO>
</ne:myWS>
My payload
#PayloadRoot(localPart = "myWSRequest", namespace = TARGET_NAMESPACE)
public #ResponsePayload myWSResponse getInfo(#RequestPayload myWSRequest request)
{
myWSResponse response = new myWSResponse();
Person person = personService_i.getAccountDetails(request.getID());
response.setPersonDetails(person);
return response;
}
Any side info?
i use xsd which generates a load of classes based upon the XSDs I don't know how to add those custom headers to the response message,
You could implement a endpointInterceptorAdapter and do the following:
public final class MyEndpointInterceptorAdapter extends EndpointInterceptorAdapter {
#Override
public boolean handleResponse(MessageContext messageContext_, Object endpoint_)
throws IOException {
WebServiceMessage _webServiceMessage = messageContext_.getResponse();
SoapMessage _soapMessage = (SoapMessage) _webServiceMessage;
if (_soapMessage != null) {
SoapEnvelope _soapEnvelope = _soapMessage.getEnvelope();
// create your qname object
QName _myQName = ....
// adding your quname to the header
_soapEnvelope.getHeader().addHeaderElement(myQName );
}
}
}
and in your spring configuration file, just add the interceptor:
<sws:interceptors>
<ref bean="myEndpointInterceptorAdapter"/>
</sws:interceptors>
In my JAXWS web service I need to send a specific message back to the client when an exception occurs, not the standard Fault message with the description of the exception.
How can this be done?
I am using jaxws-rt version 2.1.3
I have already tried to use exception mappers like this but without luck (some how they are not called, this could also be caused by a mistake in the configuration):
#Provider
public class ThrowableExceptionMapper implements ExceptionMapper<Throwable> {
public ThrowableExceptionMapper() {
// TODO Auto-generated constructor stub
}
#Override
public Response toResponse(Throwable throwable) {
if (throwable instanceof WebApplicationException) {
return ((WebApplicationException) throwable).getResponse();
} else {
return Response.serverError().entity("").build();
}
}
}
The server we use is JBoss EAP 6.4.
Edit:
The exception mapper approach is not suited for my JAXWS web service, because this is for JAX-RS (thanks SRINIVAS K).
Is there something similar available for JAXWS?
I managed to rewrite the response message with the help of this page:
http://www.java-tips.org/java-ee-tips-100042/156-java-api-for-xml-web-services/1958-writing-a-handler-in-jax-ws.html
And with some examples of this page:
http://www.programcreek.com/java-api-examples/index.php?api=javax.xml.ws.handler.MessageContext
I put together this class:
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> {
private final String RejectionResponseBody = "<ns2:MessageControllerResponse xmlns:ns2=\"http://some.namespace.com/\"><return>SOMEDATA</return></ns2:MessageControllerResponse>";
#Override
public boolean handleMessage(LogicalMessageContext context) {
return true;
}
#Override
public boolean handleFault(LogicalMessageContext context) {
processMessage(context);
return true;
}
#Override
public void close(MessageContext context) {
}
private void processMessage(LogicalMessageContext context) {
Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
LogicalMessage msg = context.getMessage();
msg.setPayload(new StreamSource(new ByteArrayInputStream(RejectionResponseBody.getBytes())));
}
}
}
Edit, additional information added:
You also need to add the HandlerChain annotation to the web service:
...
#HandlerChain(file = "handler-chain.xml")
public class MyWebService {
...
}
And create a handler-chain xml file:
<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-class>my.package.ws.jaxws.MyLogicalHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
And place this file in your resources folder of the web service, you need to use the same package structure as you did with the web service. So create the following package: my.package.ws in the resources folder if your web service class resides in the my.package.ws package.
Can you try using below?
String errorMessage = "this is custom error message";
return Response.status(e.getResponse().getStatus()).entity(errorMessage).build();
Can you check if ExceptionMapper works with generic exceptions like Throwable or Exception. I see some examples with custom defined exceptions that are configured with WebFault annotation.
Using CXF interceptor, I want to alter the response body which is being sent to the client as response. (I want to add some more nodes)
My interceptor is configured as an outgoing interceptor, is being invoked but the body is empty.
public class AlterOutBodyInterceptor extends AbstractSoapInterceptor {
private SAAJOutInterceptor saajOut = new SAAJOutInterceptor();
public AlterOutBodyInterceptor() {
super(Phase.PRE_MARSHAL);
getBefore().add(SAAJOutInterceptor.class.getName());
}
public void handleMessage(SoapMessage message) throws Fault {
SOAPMessage doc = message.getContent(SOAPMessage.class);
if (doc == null) {
saajOut.handleMessage(message);
doc = message.getContent(SOAPMessage.class);
// it is executed
}
try {
SOAPBody body = doc.getSOAPBody();
// here, body doesn't contain anything that the client gets
} catch (SOAPException e) {
e.printStackTrace();
}
}
}
I am doing trial-error with getBefore - getAfter and Phases, but without luck. Thanks in advance!
The solution is as here:
(we checked cxf sources because it contains a lot of interceptors, we got the ideas from there)
public class Interceptor extends AbstractPhaseInterceptor<Message> {
private ObjectFactory objectFactory = new ObjectFactory();
public Interceptor() {
super(Phase.PRE_LOGICAL);
addBefore(HolderOutInterceptor.class.getName());
}
#Override
public void handleMessage(Message message) {
MessageContentsList outObjects = MessageContentsList.getContentsList(message);
Assert.isTrue(outObjects.size() == 1);
Object outObject = outObjects.get(0);
// object is our soap response
}
}