I have a Camel CxfEndpoint Service defined. The Reception of the messages works fine, but the Response/Acknowledgement Message I am producing has a problem. The WS-Security parts/actions in the message are left in and therefore in the response I have my own WS-Security Parts (Signature Timestamp) plus the WS-Security Parts from the caller/original message.
The Message Acknowledgement is not accepted from the original caller and I suspect that this is the problem (that I have their Signature wth BinarySecuritySessionToken and our own).
The Camel route is rather simple for trying to resolve the issue:
from("myEndpoint")
.transacted()
.process(new PreProcessor())
.to("mock:end")
I have defined the Camel CxfEndpoint in the route as:
CxfEndpoint cxfEndpoint = new CxfEndpoint();
cxfEndpoint.setAddress("http://0.0.0.0:8888/services/Service");
cxfEndpoint.setWsdlURL("Service.wsdl");
cxfEndpoint.setCamelContext(camelContext);
....
Problem example Timestamp:
<wsu:Timestamp wsu:Id="TS-6757512FE17DCDC903153191998160526">
<wsu:Created>2018-07-18T13:19:41.605Z</wsu:Created>
<wsu:Expires>2018-07-18T13:24:41.605Z</wsu:Expires>
</wsu:Timestamp>
<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="uuid-b2a1c0b2-8263-4afc-bc99-f8a46da80ce7-693">
<u:Created>2018-07-18T13:19:42.905Z</u:Created>
<u:Expires>2018-07-18T13:24:42.905Z</u:Expires>
</u:Timestamp>
The general structure of the response message seems to be fine, but I need to strip the WS-Security Action Parts from the message.
Is there a way to strip these parts or do I need to construct a entirely new message?
Please let me know if you need additional information, thanks.
So I fixed it by adding another Interceptor for removing the Security Header.
I would like to know if this is a acceptable approach or if there is a better solution to this problem.
public class RemoveSecurityHeadersOutInterceptor extends AbstractSoapInterceptor
{
public RemoveSecurityHeadersOutInterceptor(String phase) {
super(Phase.PRE_PROTOCOL);
}
public void handleMessage(SoapMessage message) throws Fault
{
List<Header> headers = message.getHeaders();
headers.removeIf(h -> h.getName().getLocalPart().equals("Security"));
}
}
Related
I have a Reactive Spring Application using WebFlux with a REST API. Whenever a user calls my API, I need to make a call to a SOAP service which exposes a WSDL, perform some operation and return the result.
How do I combine this call to a SOAP service with the Reactive WebFlux framework?
The way I see it, I can do it 2 different ways:
Construct and send the SOAP message using WebFlux' WebClient.
Wrapping a synchronous call using WebServiceGatewaySupport in a Mono / Flux.
The first approach has my preference, but I don't know how to do that.
Similar questions have been asked here:
Reactive Spring WebClient - Making a SOAP call, which refers to this blog post (https://blog.godatadriven.com/jaxws-reactive-client). But I could not get that example to work.
Using wsdl2java in a Gradle plugin I can create a client interface with asynchronous methods, but I don't understand how to use this. When using the WebServiceGatewaySupport I don't use that generated interface or its methods at all. Instead, I call the generic marshalSendAndReceive method
public class MySoapClient extends WebServiceGatewaySupport {
public QueryResponse execute() {
Query query = new ObjectFactory().createQuery();
// Further create and set the domain object here from the wsdl2java generated classes
return (QueryResponse) getWebServiceTemplate().marshalSendAndReceive(query);
}
}
Can anyone share a complete example going from a WebFlux controller to making a SOAP call and returning asynchronously? I feel like I am missing something crucial.
I had the same aim but without having WSDL file. As an input I had endpoint and XSD file that defines request's scheme that I should to send. Here is my piece of code.
First let's define our SOPA WebClient bean (to avoid creating it each time when we want to make a call)
#Bean(name = "soapWebClient")
public WebClient soapWebClient(WebClient.Builder webClientBuilder) {
String endpoint = environment.getRequiredProperty(ENDPOINT);
log.info("Initializing SOAP Web Client ({}) bean...", endpoint);
return webClientBuilder.baseUrl(endpoint)
.defaultHeader(CONTENT_TYPE, "application/soap+xml")
//if you have any time limitation put them here
.clientConnector(getWebClientConnector(SOAP_WEBCLIENT_CONNECT_TIMEOUT_SECONDS, SOAP_WEBCLIENT_IO_TIMEOUT_SECONDS))
//if you have any request/response size limitation put them here as well
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(MAX_DATA_BUFFER_SIZE))
.build())
.build();
}
public static ReactorClientHttpConnector getWebClientConnector(int connectTimeoutSeconds, int ioTimeoutSeconds) {
TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutSeconds * 1000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(ioTimeoutSeconds))
.addHandlerLast(new WriteTimeoutHandler(ioTimeoutSeconds)));
return new ReactorClientHttpConnector(HttpClient.from(tcpClient));
}
And now you can use the client to make SOAP calls like this:
#Slf4j
#Component
public class SOAPClient {
private final WebClient soapWebClient;
public SOAPClient(#Qualifier("soapWebClient") WebClient soapWebClient) {
this.soapWebClient = soapWebClient;
}
public Mono<Tuple2<HttpStatus, String>> send(String soapXML) {
return Mono.just("Request:\n" + soapXML)
.doOnNext(log::info)
.flatMap(xml -> soapWebClient.post()
.bodyValue(soapXML)
.exchange()
.doOnNext(res -> log.info("response status code: [{}]", res.statusCode()))
.flatMap(res -> res.bodyToMono(String.class)
.doOnNext(body -> log.info("Response body:\n{}", body))
.map(b -> Tuples.of(res.statusCode(), b))
.defaultIfEmpty(Tuples.of(res.statusCode(), "There is no data in the response"))))
.onErrorResume(ConnectException.class, e -> Mono.just(Tuples.of(SERVICE_UNAVAILABLE, "Failed to connect to server"))
.doOnEach(logNext(t2 -> log.warn(t2.toString()))))
.onErrorResume(TimeoutException.class, e -> Mono.just(Tuples.of(GATEWAY_TIMEOUT, "There is no response from the server"))
.doOnEach(logNext(t2 -> log.warn(t2.toString()))));
}
}
An important thing to mention here is that your soapXML should be in the format that defined by SOAP protocol obviously. To be more specific the message at least should starts and ends with soap:Envelope tag and consist all other data inside. Also, pay attention what version of SOAP protocol you are about to use as it defines what tags are allowed to use within the envelop and what not. Mine was 1.1 and here is specification for it
https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383494
cheers
After lots of pain and trouble I found a decent solution to this problem. Since a wsdl file is provided, you should visit this site: : https://www.wsdl-analyzer.com
you can input a wsdl file and view all operations of the soap service. once you find the desired operation you want to call, click on it, and it will show an example request in xml. Some how, you have to generate this xml to make the request. There are many methods to do so, and some are more complicated than others. I found that manual serialization works well, and is honestly easier than using libraries.
say you have an operation request like this:
<s11:Envelope>
<s11:body>
<s11:operation>
<ns:username>username</ns:username>
<ns:password>password</ns:password>
</sll:operation>
</s11:body>
<s11:Envelope>
then you would generate by
public String gePayload(String username, String password) {
StringBuilder payload = new Stringbuilder();
payload.append("<s11:Envelope><s11:body><s11:operation>");
payload.append("<ns:username>");
payload.append(username);
payload.append("</ns:username>");
payload.append("<ns:password>");
payload.append(password);
payload.append("</ns:password>");
payload.append("</s11:operation></s11:body></s11:Envelope>");
return payload.toString()
}
then the web calls
public String callSoap(string payload) {
Webclient webclient = Webclient.builder()
// make sure the path is absolute
.baseUrl(yourEndPoint)
.build()
return WebClient.post()
.contentType(MediaType.TEXT_XML)
.bodyValue(payload)
.retrieve()
.bodyToMono(String.class)
.block();
}
it is important that you specify the content type is xml, and that the class returns a string. web flux cannot easily convert xml to user defined classes. so you do have to preform manual parsing. You can specify jaxb2xmlEncoders and jaxb2xmlDecoders to endcode/decode a specific class, but I found this to be to complicated. the payload has to match the request format generated by wsdl analyzer, and getting the encoders/decoders to match that format can be a task of its own. you can further research these encoders if you want, but this method will work.
I'm facing the same problem for a week and still can't find the best solution.
If you want to test the WebClient you just need to post a string with the SOAP Envelope request. Something like that:
String _request = "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">\n" +
"<soap:Body>\n" +
"<request>\n" +
"<Example>blabla</Example>\n" +
"</request>\n" +
"</soap:Body>\n" +
"</soap:Envelope>";
WebClient webClient = WebClient.builder().baseUrl("http://example-service").build();
Mono<String> stringMono = webClient.post()
.uri("/example-port")
.body(BodyInserters.fromObject(_request))
.retrieve()
.bodyToMono(String.class);
stringMono.subscribe(System.out::println);
The problem is that you need to figure out how to serialize the whole SOAP Envelope (request and response) to a string.
This is only an example - not a solution.
I'm trying to realize following route in Apache Camel:
Endpoint 1 receiving a message. The route initiates an request to Endpoint 2 (clear Body). The Transformer transforms Body of Endpoint 1 incomming Message by using the result of Endpoint 2.
I tried following:
from("direct:MessageEndpoint1")
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://localhost:9003/MessageEndpoint2")
.process(new MessageTransformationProcessor())
.to("direct:MessageEndpoint3");
The problem is, that body get overridden by message endpoint 2. I was thinking about backing up the message body into a property. But what ist best practise?
The Enterprise Integration Pattern you should be using here is enricher.
See: http://camel.apache.org/content-enricher.html
As you correctly identified, the body from Message Endpoint 1 is lost when you send the message to Message Endpoint 2.
Instead, you should try this pattern:
from("direct:MessageEndpoint1")
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.enrich("http://localhost:9003/MessageEndpoint2", new AggregationStrategy() {
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
// write an aggregation strategy that makes sense here...
}
})
.process(new MessageTransformationProcessor())
.to("direct:MessageEndpoint3");
Commonly, you can take the information you need in the response from newExchange, and set it as a property on oldExchage
oldExchange.setProperty("xyz", newExchange.getIn().getBody().get(...));
I am using Camel in our project and requesting WebServices, the dataFormat is POJO. I was able to request when my SOAP message did not contain SOAP headers, but when it had Headers, I was unable to set those. I looked at the documentation but was not able to understand and have several questions.
I want to create a message like the below:
<soapenv:Envelope`enter code here`
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>
<platformMsgs:documentInfo
xmlns:platformMsgs="urn:messages_2015_1.platform.webservices.netsuite.com">
<platformMsgs:nsId>WEBSERVICES_3479023</platformMsgs:nsId>
</platformMsgs:documentInfo>
</soapenv:Header>
<soapenv:Body>
<addListResponse
xmlns="">
<platformMsgs:writeResponseList
xmlns:platformMsgs="urn:messages_2015_1.platform.webservices.netsuite.com">
<platformCore:status isSuccess="true"
xmlns:platformCore="urn:core_2015_1.platform.webservices.netsuite.com"/>
<platformMsgs:writeResponse>
<platformCore:status isSuccess="false"
xmlns:platformCore="urn:core_2015_1.platform.webservices.netsuite.com">
<platformCore:statusDetail type="ERROR">
<platformCore:code>DUP_ENTITY</platformCore:code>
<platformCore:message>This entity already exists.</platformCore:message>
</platformCore:statusDetail>
</platformCore:status>
</platformMsgs:writeResponse>
</platformMsgs:writeResponseList>
</addListResponse>`enter code here`
</soapenv:Body>
</soapenv:Envelope>
I will be able to send the message if there was only Body, but can someone give me a code snippet for including the header section? The dataFormat is POJO.
When using CXF endpoint with dataFormat as POJO, body in Camel Exchange object is an object of org.apache.cxf.message.MessageContentsList. It is an extension of java.util.ArrayList<Object> and it contains parts of SOAP Message in order as defined in WSDL and corresponding method in WebService class.
Element 0 there is a Body.
So, one way to do that with Java is to create a Processor class implementing org.apache.camel.Processor interface and in its process method set your SOAP header. Something like:
#Override
public void process(Exchange camelExchange) throws Exception {
MessageContentsList messageBody = (MessageContentsList) camelExchange.getIn().getBody();
DocumentInfo docInfoHeader = new DocumentInfo();
... set docInfoHeader properties ...
messageBody.add(docInfoHeader);
}
(sample is not tested. It is just an idea, how to handle that...)
Other answer on similar question you can find here: Setting Custom Soap Header-To Pojo Message In Camel Cxf
It describes how to use Camel Exchange headers as SOAP Headers.
I'm not sure for 100% which way will work for you and which one is better...
I guess, it depends on WSDL you use.
UPD: second choice is to use pure CXF solution by using CxfMessageSoapHeaderOutInterceptor custom implementation.
It may look like:
public class MyCxfInterceptor extends CxfMessageSoapHeaderOutInterceptor {
#Override
public void handleMessage( org.apache.cxf.binding.soap.SoapMessage message) {
org.apache.cxf.binding.soap.SoapHeader myCustomHeader = new org.apache.cxf.binding.soap.SoapHeader(new QName(
{custom name space}, {custom local name}), {Custom content object}));
myCustomHeader.setMustUnderstand(true);
message.getHeaders().add(myCustomHeader);
}
and set Interceptor in Camel Cxf Endpoint as :
<cxfEndpoint ...>
<outInterceptors>
<spring:bean class="MyCxfInterceptor"/>
</outInterceptors>
...
Well suppose I request the Web Service and it failed, a Fault message is generated. Will I get the Fault object at position 0 of MessageContentsList then too? Or will I get only the response object at position 0?
In an incoming soap request there is a soap:mustUnderstand="1" element in soap header ,how can I handle this in my web service . If soap:mustUnderstand="1" it throws exception when it is 0 (soap:mustUnderstand="0") it runs as expected .
this is my partial soap request is like this
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header xmlns="http://www.xxxxxxx/zzzzz-msg/schema/msg-header-1_0.xsd">
<MessageHeader ResponseRequested="true" version="1.0" Terminate="true" Reverse="true" id="0002P559C1" soap:mustUnderstand="1">
.......
......
I am using Apache CXF for web service .
Your service should explicitly tell CXF that the given header has been understood and processed.
One way of doing it is registering a subclass of SOAPHandler responsible for actual processing of you header. In that interface it's important to implement method Set<QName> getHeaders() and return a set of headers' names that your handler takes care about.
CXF will then treat all those headers as understood
Example:
in Spring context XML:
<jaxws:endpoint ...>
<jaxws:handlers>
<bean class="example.MySOAPHandler" />
</jaxws:handlers>
</jaxws:endpoint>
in Java code:
public class MySOAPHandler implements SOAPHandler<SOAPMessageContext> {
public static final String MY_NS_URI = "http://www.xxxxxxx/zzzzz-msg/schema/msg-header-1_0.xsd";
public static final String MY_HEADER_NAME = "MessageHeader";
#Override
public Set<QName> getHeaders() {
// This will tell CXF that the following headers are UNDERSTOOD
return Collections.singleton(new QName(MY_NS_URI, MY_HEADER_NAME));
}
// other handler methods here
}
If a header block is annotated with mustUnderstand="1" and the
receiver wasn't designed to support the given header, the message
shouldn't be processed and a Fault should be returned to the sender
(with a soap:MustUnderstand status code). When mustUnderstand="0" or
the mustUnderstand attribute isn't present, the receiver can ignore
those headers and continue processing. The mustUnderstand attribute
plays a central role in the overall SOAP processing model.
For details kindly refer to this link
As I have noticed, in my CXF JaxRS service, if I throw an exception in say READ phase (IN interceptor) and do not provide any default FaultOutInterceptor, the XMLFaultOutInterceptor takes care of building the response as it should be returned (which is always an XML).
Now, I would like to be able to return a response in the format in which the service was requested : JSON or XML or otherwise.
I found something like this on the web:
public class JsonFaultOutHandlerInterceptor extends JAXRSOutInterceptor
{
public JsonFaultOutHandlerInterceptor() {
getBefore().add(LoggingOutInterceptor.class.getName());
}
#Override
public void handleMessage(Message message) {
...
message.getInterceptorChain().abort();
}
}
I have configured it in the outInterceptor, should I conditionally abort (if the request type was application/Json) or not abort(if the request type was application/xml) the interceptor chain? (I'm not sure if the request type information is already available. Also, somehow, aborting the chain doesn't seem very correct)
Had the response reached the JAXRS filters, using ExceptionMapper<T> I would have beautifully handled the response. But when the exception occurs in the INinterceptor, I am a little lost.
What would be a good way to be able to define a FaultOutInterceptor?