I need to get the SOAP message from exchange object I receive in my spring bean.
I have a camel route, which routes from service endpoint to my java bean. Java Bean and Camel route declared in spring looks like this:
<bean id="processor" class="com.groupgti.esb.camel.wrapper.gradireland.userregistration.UserRegistrationProcessor">
<camel:route id="route">
<camel:from uri="cxf:bean:myListenerEndpoint?dataFormat=PAYLOAD&synchronous=true" />
<camel:bean ref="processor" />
<camel:to uri="cxf:bean:myTargetEndpoint"/>
</camel:route>
In my java bean I receive the exchange object:
#Override
public SOAPMessage processMessage(Exchange exchange) {
Object object = exchange.getIn().getHeaders().get("CamelCxfMessage");
LOGGER.debug("Object: " + object);
SOAPMessage message = null;
if (object instanceof SOAPMessage) {
message = (SOAPMessage) object;
LOGGER.debug("Got message: " + message);
}
LOGGER.debug("Sending message...");
return message;
}
The problem is that I can not get the SOAP message out of the exchange. I camel web site, here I found that I have to use this to get the SOAP message:
SOAPMessage soapMessage = (SOAPMessage) exchange.getIn().getBody(List.class).get(0);
But his gives me NullPointerException somewhere deep in exchange.
I have tried to debug and see the object tree. I found that I can get the message like this:
SOAPMessage soapMessage = (SOAPMessage) exchange.getIn().getHeaders().get("CamelCxfMessage");
But this gives me this exception:
org.apache.camel.ExpectedBodyTypeException: Could not extract IN message body as type: interface javax.xml.transform.Source body is: null
I am stuck here. Maybe someone know where can be the problem?
PAYLOAD mode is not quite the easy way, maybe you should try the POJO mode for CXF, and get rid of SoapMessage. You will have to declare a POJO with JAXB annotations, like
#XmlAccessorType(XmlAccessType.FIELD)
public class Registration {
private Long roomNumber;
...
}
This will allow you to work directly on the Registration class in your processors (which I assume is your final goal).
Registration registration = exchange.getIn().getBody(Registration.class);
If you persist with PAYLOAD mode, note that you can write
SOAPMessage soapMessage = exchange.getIn().getHeader(CxfConstants.CAMEL_CXF_MESSAGE, SOAPMessage.class);
Related
I'm trying to implement a soap service consumer in Java, using spring WebServiceGatewaySupport.
When I'm using curl to consume the service as below, it is giving proper response.
curl -d #request.xml -H 'SOAPAction:abc:mnEvent#DoAction' https://myhost.org/cd/doAction.jsp
I'm trying to implement the same using JAVA, by adding following HttpHeaders in a template class inheriting from WebServiceGatewaySupport
public O callWebService(String url, I request) {
return (O) getWebServiceTemplate().marshalSendAndReceive(url, request, new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage message) {
TransportContext transportContext = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection = (HttpComponentsConnection) transportContext.getConnection();
connection.getHttpPost().addHeader("SOAPAction", "abc:mnEvent#DoAction");
}
});
}
With this code, I'm getting an error message like below.
SOP-330006 The method 'DoAction, ""' is not defined in SOAP service 'abc:mnEvent'.
What do I miss here when moving curl command to JAVA?
The error message SOP-330006 The method 'DoAction, ""' is not defined in SOAP service 'abc:mnEvent'. indicates, there are two soap actions in the request.
Explicit SoapAction added in HttpHeader
Implicit SoapAction in SoapMessage
To avoid this issue, we need to remove the soapAction from header and set it in SoapMessage.
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
soapMessage.setSoapAction("abc:mnEvent#DoAction");
I intercept the incoming and outgoing soap messages using a SoapHandler like this:
#Override
public boolean handleMessage(SOAPMessageContext context) {
boolean isResponse = (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if(!isResponse){
logger.debug("MySoapHandler.handleMessage(): this is a soap request");
SOAPMessage soapMsg = context.getMessage();
SOAPMessage = createNewSoapRequest(soapMsg);
context.setMessage(newSoapMsg);
}
else {
logger.debug("MySoapHandler.handleMessage(): this is a soap response");
SOAPMessage soapMsg = context.getMessage();
SOAPMessage newSoapMsg = createNewSoapResponse(soapMsg);
context.setMessage(newSoapMsg);
}
return true;
}
I receive the incoming soap message and replace it with a new one and send it on its way. I can monitor the incoming message and its replacement and everything looks right. If I comment out the incoming part and test the outgoing part by sending the correct request using SoapUI, the outgoing soap response is intercepted and replaced with a new message. That too seems to work ok.
When I un-comment the incoming portion and let er' rip, the incoming one is triggered correctly, but the outgoing response seems to get triggered prematurely, before the main code for generating it has completed. Can anyone shed some light?
I created a proxy-camel which accepts SOAP (over HTTP) and RESTful requests and forwards them to the correct web service. The Camel is unaware of message-structure, it doesn't know the WSDL or anything, it just knows if it is SOAP or not according to a http header. There is no CXF endpoint.
Further it does some Processing. Exception can occur inside there, for example when a service is not found or the url is invalid.
Is there an easy way to return a valid SOAPFault directly from this camel?
I tried to write a simple processor which is called onException. It looks like this:
.choice().when().header("SOAP").processRef(ExceptionToSoapProcessor())
The Processor that should transform any Exception into a SOAPFault looks like this
#Override
public void process(Exchange exchange) throws Exception {
Exception exception = (Exception) exchange.getProperty(Exchange.EXCEPTION_CAUGHT);
Integer responseCode = (Integer) exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE);
QName qName = SoapFault.FAULT_CODE_SERVER;
if (responseCode != null && responseCode < 500) {
qName = SoapFault.FAULT_CODE_CLIENT;
}
SoapFault fault = new SoapFault(exception.getMessage(), qName);
Message outMessage = exchange.getOut();
outMessage.setHeader(Message.RESPONSE_CODE, 500);
outMessage.setFault(true);
outMessage.setBody(fault);
exchange.setException(null);
exchange.removeProperty(Exchange.EXCEPTION_CAUGHT);
exchange.setProperty(Exchange.EXCEPTION_HANDLED, true);
}
But now I don't understand how I will marshal it, the response looks like this:
org.apache.cxf.binding.soap.SoapFault: Unauthorized
("Unauthorized" is the actual message)
PS: I used the dataformat SOAP before, but as mentioned, I don't have any ServiceInterface in this Camel.
I would move the handling of the error scenario to an onException() block. That way you can "declare" some of the behavior, like marking the exception as handled. IMHO makes it a little cleaner.
Just returning the SOAP fault would not result in a valid SOAP response. You have to build the complete message structure. I don't think there is a type converter for SOAP messages to a text stream, so you have to marshal the SOAP response yourself.
This is the code I am using to do the job:
<onException>
<exception>java.lang.Exception</exception>
<handled>
<constant>true</constant>
</handled>
<bean beanType="some.package.WSHelper" method="createSOAPFaultServerError" />
</onException>
public static String createSOAPFaultServerError(final Exception cause) {
String result = null;
LOG.error("Creating SOAP fault, hiding original cause from client:", cause);
try {
SOAPMessage message = MessageFactory.newInstance().createMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPBody body = message.getSOAPBody();
SOAPFault fault = body.addFault();
fault.setFaultCode("Server");
fault.setFaultString("Unexpected server error.");
Detail detail = fault.addDetail();
Name entryName = envelope.createName("message");
DetailEntry entry = detail.addDetailEntry(entryName);
entry.addTextNode("The server is not able to complete the request. Internal error.");
result = soapMessage2String(message);
} catch (Exception e) {
LOG.error("Error creating SOAP Fault message", e);
}
return result;
}
private static String soapMessage2String(final SOAPMessage message) throws SOAPException, IOException {
String result = null;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
message.writeTo(outStream);
result = new String(outStream.toByteArray(), StandardCharsets.UTF_8);
return result;
}
HTH
I am using a jax-ws Handler to intercept a SOAP message so that I can grab certain elements from the Header. Is there a way to deserialize a Header and parse it into either wsdl2java or xmlbeans generated object? I am currently migrating from AXIS to CXF. Under AXIS, I could get the Header xml and parse it using a Factory class. I have not found a good way to do this with the objects generated from CXF wsdl2java. I realize that I can use the getElementsByTagName to retrieve each Node, but it is just easier to work with the actual object.
You can use SAAJ API to manipulate the SOAP message directly. Extend AbstractSoapInterceptor by implementing the handleMessage() method and add it to the chain of InInterceptors.
public class MyCustomInInterceptor extends AbstractSoapInterceptor {
public void handleMessage(SoapMessage soapMessage) throws Fault {
try {
SOAPMessage message = soapMessage.getContent(SOAPMessage.class);
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope se = sp.getEnvelope();
SOAPBody sb = se.getBody();
SOAPHeader sh = se.getHeader();
}
catch(Exception e)
{
log.error(e);
throw new Fault(e);
}
}
}
NOTE: You will also need to attach SAAJInInterceptor to the chain of interceptors or else soapMessage.getContent(SOAPMessage.class) will return null
I have a spring-ws (2.0.2) service I have implemented that requires some custom elements in the soap header. I am trying to use Spring's MockWebServiceClient to generate a valid request to test the dispatcher, marshallers, etc.
The problem I am getting is that the MockWebSerivce only seems to support the Soap Body (the payload).
How can I access the soap request being generated to get the right headers into it?
If there is a better library for doing this other than Spring's MockWebServiceClient, that would be fine too.
Related Links:
http://forum.springsource.org/showthread.php?101708-MockWebServiceClient-amp-WS-Security
Add SoapHeader to org.springframework.ws.WebServiceMessage
I had the similar problem when I wanted to test spring web service with Security, I ended up using the Spring Interceptors to modify the header before they reach end point, I enabled the interceptors only for testing.
Create an interceptor, I implemented the SmartEndpointInterceptor, You can use the other interceptors if you choose
public class ModifySoapHeaderInterceptor implements
SmartEndpointInterceptor
{
//WSConstants.WSSE_NS;
private static final String DEFAULT_SECURITY_URL = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String SECURITY_TAG = "Security";
private static final String SECURITY_PREFIX = "wsse";
private static final String USER_NAME_TOKEN = "UsernameToken";
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws Exception
{
SaajSoapMessage saajSoapMessage(SaajSoapMessage)messageContext.getRequest());
SOAPHeader soapHeader = saajSoapMessage.getSaajMessage().getSOAPPart()
.getEnvelope().getHeader();
//you can modify header's as you choose
Name headerElement = saajSoapMessage.getSaajMessage().getSOAPPart()
.getEnvelope()
.createName(SECURITY_TAG, SECURITY_PREFIX, DEFAULT_SECURITY_URL);
SOAPHeaderElement soapHeaderElement = soapHeader
.addHeaderElement(headerElement);
SOAPElement usernameToken = soapHeaderElement.addChildElement(
USER_NAME_TOKEN, SECURITY_PREFIX);
SOAPElement userNameElement = usernameToken.addChildElement("Username",
SECURITY_PREFIX);
userNameElement.addTextNode("userid");//you can inject via spring
SOAPElement passwordElement = usernameToken.addChildElement("Password",
SECURITY_PREFIX);
passwordElement.addTextNode("password");
return true;
}
}
Configure this interceptor in spring context
<sws:interceptors>
<bean class="prasanna.ws.security.wss4j.ModifySoapHeaderInterceptor"/>
</sws:interceptors>
This will add the necessary security headers to the message before it reaches the end point, You can still use MockWebServiceClient to test your web service.
As you noted, the MockWebServiceClient sendRequest() method only sets up the SOAP body with the payload given it. It does not touch the SOAP header.
To set up the SOAP header as well you can create a class that implements the RequestCreator interface and sets the SOAP header. Pass an instance of this class to the sendRequest() method.
For example:
class SoapActionCreator implements RequestCreator {
private final Source payload;
public SoapActionCreator(Source payload) {
this.payload = payload;
}
#Override
public WebServiceMessage createRequest(WebServiceMessageFactory webServiceMessageFactory)
throws IOException {
WebServiceMessage webServiceMessage =
new PayloadMessageCreator(payload).createMessage(webServiceMessageFactory);
SoapMessage soapMessage = (SoapMessage) webServiceMessage;
SoapHeader header = soapMessage.getSoapHeader();
// Add an Action element to the SOAP header
StringSource headerSource = new StringSource(
"<wsa:Action xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">https://example.com/foo/bar</wsa:Action>");
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(headerSource, header.getResult());
return webServiceMessage;
}
}
Then use SoapActionCreator like this:
SoapActionCreator soapActionCreator = new SoapActionCreator(requestPayload);
mockClient.sendRequest(soapActionCreator).
andExpect(payload(responsePayload));
where requestPayload is the SOAP request body and responsePayload is the entire SOAP response (header and body).
I did find the smock library which does what I really wanted: just takes a text file with the whole request in it.
http://code.google.com/p/smock/wiki/SpringWs
It supports the same request and response matchers as the spring provided stuff. It also keeps my tests very self contained. (Rather than a whole new class that would only be used in my test cases.)