Switchyard and Camel Transparent Proxy doesn't propagate SOAP header - soap

I'm trying to create a transparent proxy with Switchyard.
The idea is having a "promoted" service exposed by Switchyard. Calls to this webservice will be redirected with Camel to a service reference. Service reference (proxified service) and promoted service have the same WSDL.
The problem is that the SOAP calls that the user does contains a custom token in the SOAP Envelope Header that it is NOT propagated. How can I solve that?
SOAP Call Example (User->Propagated Service):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.test/">
<soapenv:Header>
<token>foo</token>
</soapenv:Header>
<soapenv:Body>
<ser:readSomethings>
<something>
</something>
</ser:readSomethings>
</soapenv:Body>
</soapenv:Envelope>
SOAP Call that Switchyard does to the "proxified" service (It doesn't contain the token!!):
<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ser:readSomethings xmlns:ser=\"http://service.test/\">
<something></something>
</ser:readSomethings>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
switchyard.xml:
<?xml version="1.0" encoding="ASCII"?>
<switchyard xmlns="urn:switchyard-config:switchyard:1.0" xmlns:bean="urn:switchyard-component-bean:config:1.0" xmlns:camel="urn:switchyard-component-camel:config:1.0" xmlns:sca="http://docs.oasis-open.org/ns/opencsa/sca/200912" xmlns:soap="urn:switchyard-component-soap:config:1.0" name="SomethingService" targetNamespace="urn:my.company.something:SomethingService:1.0">
<sca:composite name="SomethingService" targetNamespace="urn:my.company.something:SomethingService:1.0">
<sca:service name="PromotedProxyService" promote="ProxyService/ProxyService">
<soap:binding.soap>
<soap:contextMapper includes=".*"/>
<soap:wsdl>META-INF/SomethingWS.wsdl</soap:wsdl>
<soap:socketAddr>:${jettyPort}</soap:socketAddr>
<soap:contextPath>SomethingService</soap:contextPath>
</soap:binding.soap>
</sca:service>
<sca:reference name="ProxifiedService" multiplicity="0..1" promote="ProxyService/ProxifiedService">
<soap:binding.soap>
<soap:contextMapper includes=".*"/>
<soap:wsdl>META-INF/SomethingWS.wsdl</soap:wsdl>
<soap:endpointAddress>http://IP:8080/ws_admin/SomethingWS</soap:endpointAddress>
</soap:binding.soap>
</sca:reference>
<sca:component name="ProxyService">
<camel:implementation.camel>
<camel:java class="my.company.something.RouterCamel"/>
</camel:implementation.camel>
<sca:service name="ProxyService">
<sca:interface.wsdl interface="META-INF/SomethingWS.wsdl#wsdl.porttype(SomethingWS)"/>
</sca:service>
<sca:reference name="ProxifiedService">
<sca:interface.wsdl interface="META-INF/SomethingWS.wsdl#wsdl.porttype(SomethingWS)"/>
</sca:reference>
</sca:component>
</sca:composite>
<domain>
<properties>
<property name="org.switchyard.handlers.messageTrace.enabled" value="true"/>
</properties>
</domain>
</switchyard>
RouterCamel.java
public class RouterCamel extends org.apache.camel.builder.RouteBuilder{
public void configure() {
org.apache.camel.Processor myProc = new org.apache.camel.Processor(){
public void process(org.apache.camel.Exchange exchange) throws Exception {
System.out.println("------------------------------ ENTRO\n\n\n\n");
String body = exchange.getIn().getBody(String.class);
// change the message to say Hello
System.out.println("\n\n\n\n------------------------------ BODY: " + body);
exchange.getOut().setBody(body);
// copy headers from IN to OUT to propagate them
System.out.println("Header in values:");
for(Entry<String, Object> header: exchange.getIn().getHeaders().entrySet()){
System.out.println("Header: " + header.getKey() + " Value: " + header.getValue());
}
exchange.getOut().setHeaders(exchange.getIn().getHeaders());
System.out.println("Header out values:");
for(Entry<String, Object> header: exchange.getOut().getHeaders().entrySet()){
System.out.println("Header: " + header.getKey() + " Value: " + header.getValue());
}
}
};
// Define routing rules here:
from("switchyard://ProxyService").process(myProc).to("switchyard://ProxifiedService");
}
}

The header was not propagated because it didn't have a namespace.
I found the answer here: https://developer.jboss.org/thread/243364

<sca:reference name="ProxyReference" multiplicity="0..1" promote="Proxy/ProxyService">
<sca:interface.wsdl interface="META-INF/wsdl/RegisterService.wsdl#wsdl.porttypePortType)"/>
<soap:binding.soap name="soap">
<soap:contextMapper class="WSHeaderContextMapper"/>
<soap:wsdl>META-INF/wsdl/RegisterService.wsdl</soap:wsdl>
<soap:endpointAddress>${service.address}</soap:endpointAddress>
<soap:timeout>12000</soap:timeout>
</soap:binding.soap>
</sca:reference>
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.message.WSSecUsernameToken;
import org.switchyard.Context;
import org.switchyard.component.soap.composer.SOAPBindingData;
import org.switchyard.component.soap.composer.SOAPContextMapper;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFactory;
public class WSHeaderContextMapper extends SOAPContextMapper {
private String user = "user";
private String password = "pass";
#Override
public void mapTo(Context context, SOAPBindingData target) throws Exception {
SOAPFactory factory = SOAPFactory.newInstance();
SOAPElement element = factory.createElement("Security", "wsse", WSConstants.WSSE_NS);
WSSecUsernameToken utBuilder = createUsernameToken(this.user, password);
utBuilder.prepare(element.getOwnerDocument());
element.addChildElement(factory.createElement(utBuilder.getUsernameTokenElement()));
context.setProperty(element.getElementQName().toString(), element);
super.mapTo(context, target);
}
private WSSecUsernameToken createUsernameToken(String userName, String password) {
WSSecUsernameToken utBuilder = new WSSecUsernameToken();
utBuilder.setUserInfo(userName, password);
utBuilder.setPasswordType(WSConstants.PASSWORD_TEXT);
return utBuilder;
}
}

Related

How to handle soap:mustUnderstand header in Spring WS

An external system is sending my service a SOAP message and I have a listener in place
#Endpoint
public class NotificationListener {
private static final String NAMESPACE_URI = "http://test.com/test";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "Notification")
#ResponsePayload
public void getSOAPMessage(#RequestPayload HistoryMessage request) {
// calls to methods in other classes which handle the business logic
}
The request body contains the following headers:
<soap:Header>
<wsa:To soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">{destination endpoint}
</wsa:To>
<wsa:From
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:From>
<wsa:ReplyTo
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:FaultTo
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:FaultTo>
<wsa:Action soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">
</wsa:Action>
<wsa:MessageID
xmlns:wsa="http://www.w3.org/2005/08/addressing">urn:uuid:fa163e6e-ef55-1eec-b9ac-5e80af1d126a
</wsa:MessageID>
I get the following error on calling my endpoint:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:MustUnderstand</faultcode>
<faultstring xml:lang="en">One or more mandatory SOAP header blocks not understood</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I understand that the mustUnderstand attribute in the header is set to 1 which means true and that header must be handled. But how do I handle it?
I'm using Spring WS to build the listener service.
TIA:)

Customizing Spring Integration Web Service SOAP Envelope/Header

I am trying to send a SOAP request using Spring Integration like
<int:chain input-channel="wsOutChannel" output-channel="stdoutChannel">
<int-ws:header-enricher>
<int-ws:soap-action value="..."/>
</int-ws:header-enricher>
<int-ws:outbound-gateway
uri="..."/>
</int:chain>
but you can only add the SOAP body, and Spring Integration adds the envelope, header, and body tags like
<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
<SOAP-ENV:Header>
</SOAP-ENV:Envelope>
I need to customize the envelope and header tags with specific attributes, for example:
<soapenv:Envelope attribute1="value1" attribute2="value2">
and child elements, for example:
<soapenv:Header>
<child>...<child>
<soapenv:Header>
Is this possible with Spring Integration Web Services, or should I not use int-ws:outbound-gateway and take a different approach?
You can add a ClientInterceptor (via the interceptor attribute) which allows you to modify the request before it's sent out.
EDIT
#Artem's suggestion is simpler but the interceptor gives you access to the response too; but either way, the code is similar.
For the interceptor:
public class MyInterceptor extends ClientInterceptorAdapter {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
SoapMessage request = (SoapMessage) messageContext.getRequest();
SoapEnvelope envelope = request.getEnvelope();
envelope.addAttribute(new QName("foo"), "bar");
SoapHeader header = envelope.getHeader();
header.addHeaderElement(new QName("http://fiz/buz", "baz"));
return super.handleRequest(messageContext);
}
}
For the callback version:
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SoapEnvelope envelope = ((SoapMessage) message).getEnvelope();
envelope.addAttribute(new QName("foo"), "bar");
SoapHeader header = envelope.getHeader();
header.addHeaderElement(new QName("http://fiz/buz", "baz"));
}
I thing you can inject WebServiceMessageCallback:
<xsd:attribute name="request-callback" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Reference to a Spring Web Services WebServiceMessageCallback. This enables changing
the Web Service request message after the payload has been written to it but prior
to invocation of the actual Web Service.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.ws.client.core.WebServiceMessageCallback"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
and cast the message to the SoapMessage and use its getEnvelope() to customize a desired way.

CXF log SOAP output

I am having trouble logging an outgoing SOAP message from the server. The handleMessage method does not overwrite the message content as expected. How would I store the outgoing SOAP to the message?
public class OutgoingSoapInterceptor extends AbstractPhaseInterceptor<Message> {
private static final Logger logger = LoggerFactory.getLogger(OutgoingSoapInterceptor.class.getName());
public OutgoingSoapInterceptor ()
{
super(Phase.PRE_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
logger.debug("outbound soap handleMessage");
OutputStream os = message.getContent ( OutputStream.class );
CacheAndWriteOutputStream cwos = new CacheAndWriteOutputStream ( os);
message.setContent ( OutputStream.class, cwos );
cwos.registerCallback ( new LoggingOutCallBack ( ) );
}
}
There is a simpler way to log the SOAP messages using CXF LoggingInInterceptor and LoggingOutInterceptor
LogUtils.setLoggerClass(org.apache.cxf.common.logging.Log4jLogger.class);
yourService = new YourService(wsdlURL, SERVICE_NAME);
port = yourService.getServicePort();
Client client = ClientProxy.getClient(port);
client.getInInterceptors().add(new LoggingInInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());
Or configuring interceptors in <cxf:bus> with spring
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<cxf:bus>
<cxf:features>
<cxf:logging />
</cxf:features>
</cxf:bus>
<jaxws:endpoint ... />
</beans>
See more examples in How to log Apache CXF Soap Request and Soap Response using Log4j

Spring Integration should route Message (with SOAP Headers)

I am trying to write configure a gateway, which should take a complete SOAP Message and then delegate it to another SOAP Provider (incl. all SOAP headers of the first request).
What I have done so far:
1) web.xml
MessageDispatcherServlet with Mapping:
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/appservices/*</url-pattern>
</servlet-mapping>
2) Configuration with an Endpoint-Mapping
<bean class="org.springframework.ws.server.endpoint.mapping.UriEndpointMapping">
<property name="defaultEndpoint" ref="ws-in-gw"/>
</bean>
3) Configuration of Spring Integration inbound-gateway and outbound-gateway
<int-ws:inbound-gateway id="ws-in-gw"
request-channel="in"
reply-channel="out"
mapped-request-headers="*" />
<int:channel id="in" />
<int:channel id="out" />
<int-ws:outbound-gateway
id="ws-out-gw-status"
request-channel="in-status"
reply-channel="out-status"
uri="http://${delegationServer}/${delegation.contextroot}/soap/AnotherService"
interceptor="soapEnricher"
</int-ws:outbound-gateway>
<bean id="soapEnricher" class="foo.bar.SoapHeaderEnricher" />
public class SoapHeaderEnricher implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
try {
SoapMessage soapMessage = (SoapMessage) messageContext.getRequest();
SoapHeader sh = soapMessage.getSoapHeader();
// can use sh.addHeaderElement(new QName(...)) now, but where are the original Headers???
} catch () {
}
}
My first Problem was, that the original SOAP Headers had been cut of, so I introduced the ' mapped-request-headers="*" ' attribute at the inbound gateway.
When I now configure a wire-tap, I see the Headers (myToken:MySecretToken) are received:
DEBUG 10:46:53 - [Payload DOMSource content=javax.xml.transform.dom.DOMSource#24a6ce98][Headers={errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#43456ff4, myToken:MySecretToken=org.springframework.ws.soap.saaj.SaajSoapHeaderElement#3b91ead, ...}]
This is the SOAP Message for my test:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:stat="http://status.service.promakler.provinzial.com/">
<soapenv:Header>
<myToken:MySecretToken xmlns=""
xmlns:myToken="http://foo.bar">12345</myToken:MySecretToken>
</soapenv:Header>
<soapenv:Body>
<stat:getStatus/>
</soapenv:Body>
</soapenv:Envelope>
So the Headers are now in my Message, but in the ClientInterceptor, there is no way to get the Headers (just the payload)?! I can add new Headers, but how can I get the original Header?
Can anybody give me a hint (or perhaps there is even a quiet simpler solution??)
Regards
Timo
Try to introduce a custom extension of DefaultSoapHeaderMapper and override populateUserDefinedHeader to extract those SaajSoapHeaderElement from the MessageHeaders and populate them to the SoapHeader. And finally inject your solution to the header-mapper of your <int-ws:outbound-gateway>.

MULE ESB : Binding multiple Web Services to one Client

I have a client which connects to a Web service to get some information. I have a requirement where I have to send the same information to multiple services using different ports. To solve this without modifying the client code I found MULE ESB, which is supposed to do exactly what I need.
I've found a guide where I could connect one client to one service using MULE ESB and one port, but I cant find a way to chain the services so they all listen to one port but have different themselves.
This is how it's supposed to look like:
UPDATE :
here is my current Mule Applications config :
<mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="CE-3.2.1" xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd ">
<flow name="flows1Flow1" doc:name="flows1Flow1">
<http:inbound-endpoint exchange-pattern="request-response" address="http://localhost:4433/miniwebservice" mimeType="text/xml" doc:name="HTTP"/>
<http:outbound-endpoint exchange-pattern="request-response" address="http://localhost:4434/miniwebservice?wsdl" mimeType="text/xml" doc:name="HTTP"/>
</flow>
</mule>
Here is the WebService :
Client :
package miniwebservice;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
public class TestWsClient
{
public static void main( final String[] args ) throws Throwable
{
String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/miniwebservice";
Service service = Service.create(
new URL( url + "?wsdl" ),
new QName( "http://miniwebservice/", "HalloWeltImplService" ) );
HalloWelt halloWelt = service.getPort( HalloWelt.class );
System.out.println( "\n" + halloWelt.hallo( args.length > 1 ? args[1] : "" ) );
}
}
Server :
package miniwebservice;
import javax.xml.ws.Endpoint;
public class TestWsServer
{
public static void main( final String[] args )
{
String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/miniwebservice";
Endpoint.publish( url, new HalloWeltImpl() );
}
}
InterfaceImpl :
package miniwebservice;
import javax.jws.WebService;
#WebService( endpointInterface="miniwebservice.HalloWelt" )
public class HalloWeltImpl implements HalloWelt
{
public String hallo( String wer )
{
return "Hallo " + wer;
}
}
interface :
package miniwebservice;
import javax.jws.*;
#WebService
public interface HalloWelt
{
public String hallo( #WebParam( name = "wer" ) String wer );
}
If I start the Server and the Mule aplication and try to reach http://localhost:4434/miniwebservice?wsdl ower http://localhost:4433/miniwebservice I get the folowing Exception in my Browser (FireFox 8.0) :
Couldn't create SOAP message due to exception: XML reader error: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,1]
Message: Content is not allowed in prolog.
I just started to work with Mule so I thouth that this would be enouth to get redirect mule to the Service to get the wsdl but its seems like its a bit complicatet.
Disclaimers:
This is not the final solution to the whole problem, which includes dispatching to several services and aggregating results, but a step in the right direction.
This is not representative of how web service proxying is done in Mule (which is way simpler) but a barebone approach to HTTP request routing so aggregation can be added.
Since you want to forward HTTP GET requests to the ?wsdl processor and HTTP POST SOAP request to the web service, you need to handle the target HTTP method and request URI propagation yourself:
<flow name="flows1Flow1">
<http:inbound-endpoint exchange-pattern="request-response"
address="http://localhost:4433/miniwebservice" />
<message-properties-transformer scope="outbound">
<add-message-property key="http.method" value="#[header:INBOUND:http.method]" />
</message-properties-transformer>
<logger level="INFO" category="ddo" />
<http:outbound-endpoint exchange-pattern="request-response"
address="http://localhost:4434#[header:INBOUND:http.request]" />
</flow>
(tested and validated with TestWsClient and TestWsServer)