This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 690a5104cc3 CAMEL-21040: ensure more consistency in the document sections (#15032) 690a5104cc3 is described below commit 690a5104cc347398efa03bd0245d8c5cb38129ad Author: Otavio Rodolfo Piske <orpi...@users.noreply.github.com> AuthorDate: Tue Aug 6 21:15:11 2024 +0200 CAMEL-21040: ensure more consistency in the document sections (#15032) --- .../src/main/docs/crypto-component.adoc | 2 +- .../camel-csv/src/main/docs/csv-dataformat.adoc | 18 +- .../src/main/docs/cxfrs-component.adoc | 12 +- .../src/main/docs/cxf-component.adoc | 1618 ++++++++++---------- .../src/main/docs/dataset-component.adoc | 62 +- .../src/main/docs/datasonnet-language.adoc | 133 +- 6 files changed, 929 insertions(+), 916 deletions(-) diff --git a/components/camel-crypto/src/main/docs/crypto-component.adoc b/components/camel-crypto/src/main/docs/crypto-component.adoc index 4837da3602f..a6f91ff73ff 100644 --- a/components/camel-crypto/src/main/docs/crypto-component.adoc +++ b/components/camel-crypto/src/main/docs/crypto-component.adoc @@ -106,7 +106,7 @@ include::partial$component-endpoint-options.adoc[] include::partial$component-endpoint-headers.adoc[] // component headers: END -== Using +== Usage === Raw keys diff --git a/components/camel-csv/src/main/docs/csv-dataformat.adoc b/components/camel-csv/src/main/docs/csv-dataformat.adoc index bb8158a0e66..78964040f81 100644 --- a/components/camel-csv/src/main/docs/csv-dataformat.adoc +++ b/components/camel-csv/src/main/docs/csv-dataformat.adoc @@ -37,7 +37,9 @@ The following headers are supported by this component: |=== -== Marshalling a Map to CSV +== Examples + +=== Marshalling a Map to CSV The component allows you to marshal a Java Map (or any other message type that can be converted in a Map) into a @@ -86,7 +88,7 @@ then it will produce abc,123 ---- -== Unmarshalling a CSV message into a Java List +=== Unmarshalling a CSV message into a Java List Unmarshalling will transform a CSV message into a Java List with CSV file lines (containing another List with all the field values). @@ -122,7 +124,7 @@ for (List<String> line : data) { } -------------------------------------------------------------------------------------------------------------- -== Marshalling a List<Map> to CSV +=== Marshalling a List<Map> to CSV *Since Camel 2.1* @@ -131,7 +133,7 @@ format, you can now store the message payload as a `List<Map<String, Object>>` object where the list contains a Map for each row. -== File Poller of CSV, then unmarshaling +=== File Poller of CSV, then unmarshaling Given a bean which can handle the incoming data... @@ -158,7 +160,7 @@ public void doHandleCsvData(List<List<String>> csvData) </route> ------------------------------------------------------------------------------------------------ -== Marshaling with a pipe as delimiter +=== Marshaling with a pipe as delimiter Considering the following body: [source,java] @@ -244,7 +246,7 @@ should illustrate this customization. </bean> ----------------------------------------------------------------------------------------------------------------------------- -== Collecting header record +=== Collecting header record You can instruct the CSV Data Format to collect the headers into a message header called CamelCsvHeaderRecord. @@ -260,7 +262,7 @@ from("direct:start") -------------------------------------------- -== Using skipFirstLine or skipHeaderRecord option while unmarshaling +=== Using skipFirstLine or skipHeaderRecord option while unmarshaling *For Camel >= 2.16.5 The instruction for CSV Data format to skip headers or first line is the following. @@ -313,7 +315,7 @@ XML:: -== Unmarshaling with a pipe as delimiter +=== Unmarshaling with a pipe as delimiter [tabs] diff --git a/components/camel-cxf/camel-cxf-rest/src/main/docs/cxfrs-component.adoc b/components/camel-cxf/camel-cxf-rest/src/main/docs/cxfrs-component.adoc index 4d077681a5c..b173c61f803 100644 --- a/components/camel-cxf/camel-cxf-rest/src/main/docs/cxfrs-component.adoc +++ b/components/camel-cxf/camel-cxf-rest/src/main/docs/cxfrs-component.adoc @@ -83,7 +83,9 @@ Please check the following files for more details: * http://cxf.apache.org/docs/jax-rs.html[CXF JAX-RS documentation]. ==== -== How to configure the REST endpoint in Camel +== Examples + +=== How to configure the REST endpoint in Camel In the https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-rest/src/main/resources/schema/cxfJaxrsEndpoint.xsd[camel-cxf schema file], there are two elements for the REST endpoint definition: @@ -93,7 +95,7 @@ https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring- You can find a Camel REST service route configuration example there. -== How to override the CXF producer address from message header +=== How to override the CXF producer address from message header The `camel-cxfrs` producer supports overriding the service address by setting the message with the key of `CamelDestinationOverrideUrl`. @@ -103,7 +105,7 @@ The `camel-cxfrs` producer supports overriding the service address by setting th exchange.getIn().setHeader(Exchange.DESTINATION_OVERRIDE_URL, constant(getServiceAddress())); ---------------------------------------------------------------------------------------------- -== Consuming a REST Request - Simple Binding Style +=== Consuming a REST Request - Simple Binding Style *Since Camel 2.11* @@ -220,7 +222,7 @@ found https://svn.apache.org/repos/asf/camel/trunk/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/jaxrs/simplebinding/[here]. ==== -== Consuming a REST Request - Default Binding Style +=== Consuming a REST Request - Default Binding Style The http://cxf.apache.org/docs/jax-rs.html[CXF JAXRS front end] implements the https://javaee.github.io/jsr311/[JAX-RS (JSR-311) API], so we can @@ -314,7 +316,7 @@ public interface CustomerServiceResource { } ---- -== How to invoke the REST service through camel-cxfrs producer +=== How to invoke the REST service through camel-cxfrs producer The http://cxf.apache.org/docs/jax-rs.html[CXF JAXRS front end] implements diff --git a/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc b/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc index 4849e66e833..c646a081b07 100644 --- a/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc +++ b/components/camel-cxf/camel-cxf-soap/src/main/docs/cxf-component.adoc @@ -120,1053 +120,1059 @@ exchange property, `CamelCXFDataFormat`. The exchange key constant is defined in `org.apache.camel.component.cxf.common.message.CxfConstants.DATA_FORMAT_PROPERTY`. -== How to create a simple CXF service with POJO data format +== Usage -Having simple java web service interface: +=== RAW Mode +Attachments are not supported as it does not process the message at all. -[source,java] ----- -package org.apache.camel.component.cxf.soap.server; +=== CXF_MESSAGE Mode -@WebService(targetNamespace = "http://server.soap.cxf.component.camel.apache.org/", name = "EchoService") -public interface EchoService { +MTOM is supported, and Attachments can be retrieved by Camel Message APIs mentioned above. Note that when receiving a multipart (i.e., MTOM) message, the default `SOAPMessag`e to `String` converter will provide the complete multipart payload on the body. +If you require just the SOAP XML as a String, you can set the message body +with `message.getSOAPPart()`, and the Camel converter can do the rest of the work +for you. - String echo(String text); -} ----- +=== Streaming Support in PAYLOAD mode -And implementation: +The Camel CXF component now supports streaming of incoming +messages when using PAYLOAD mode. Previously, the incoming messages +would have been completely DOM parsed. For large messages, this is time-consuming and uses a significant amount of memory. +The incoming messages can remain as a `javax.xml.transform.Source` while +being routed and, if nothing modifies the payload, can then be directly +streamed out to the target destination. For common "simple proxy" use +cases (example: `from("cxf:...").to("cxf:...")`), this can provide very +significant performance increases as well as significantly lowered +memory requirements. -[source,java] ----- +However, there are cases where streaming may not be appropriate or +desired. Due to the streaming nature, invalid incoming XML may not be +caught until later in the processing chain. Also, certain actions may +require the message to be DOM parsed anyway (like WS-Security or message +tracing and such) in which case, the advantages of the streaming are +limited. At this point, there are two ways to control the streaming: -package org.apache.camel.component.cxf.soap.server; +* Endpoint property: you can add `allowStreaming=false` as an endpoint +property to turn the streaming on/off. -@WebService(name = "EchoService", serviceName = "EchoService", targetNamespace = "http://server.soap.cxf.component.camel.apache.org/") -public class EchoServiceImpl implements EchoService { +* Component property: the `CxfComponent` object also has an `allowStreaming` +property that can set the default for endpoints created from that +component. - @Override - public String echo(String text) { - return text; - } +Global system property: you can add a system property of +`org.apache.camel.component.cxf.streaming` to `false` to turn it off. +That sets the global default, but setting the endpoint property above +will override this value for that endpoint. -} ----- +=== Using the generic CXF Dispatch mode -We can then create the simplest CXF service (note we didn't specify the `POJO` mode, as it is the default mode): +The Camel CXF component supports the generic +https://cxf.apache.org/docs/jax-ws-dispatch-api.html[CXF dispatch mode] that can transport messages of arbitrary structures (i.e., not bound to a specific XML schema). +To use this mode, you omit specifying the `wsdlURL` and `serviceClass` attributes of the CXF endpoint. +[tabs] +==== +Java (Quarkus):: ++ [source,java] ---- - from("cxf:echoServiceResponseFromImpl?serviceClass=org.apache.camel.component.cxf.soap.server.EchoServiceImpl&address=/echo-impl")// no body set here; the response comes from EchoServiceImpl - .log("${body}"); ----- +import org.apache.camel.component.cxf.common.DataFormat; +import org.apache.camel.component.cxf.jaxws.CxfEndpoint; +import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; -For more complicated implementation of the service (more "Camel way"), we can set the body from the route instead: +... -[source,java] ----- - from("cxf:echoServiceResponseFromRoute?serviceClass=org.apache.camel.component.cxf.soap.server.EchoServiceImpl&address=/echo-route") - .setBody(exchange -> exchange.getMessage().getBody(String.class) + " from Camel route"); +@Produces +@SessionScoped +@Named +CxfEndpoint dispatchEndpoint() { + final CxfEndpoint result = new CxfEndpoint(); + result.setDataFormat(DataFormat.PAYLOAD); + result.setAddress("/SoapAnyPort"); + return result; +} ---- +XML (Spring):: ++ +[source,xml] +---- +<cxf:cxfEndpoint id="dispatchEndpoint" address="http://localhost:9000/SoapContext/SoapAnyPort"> + <cxf:properties> + <entry key="dataFormat" value="PAYLOAD"/> + </cxf:properties> +</cxf:cxfEndpoint> +---- +==== -== How to consume a message from a Camel CXF endpoint in POJO data format +It is noted that the default CXF dispatch client does not send a +specific `SOAPAction` header. Therefore, when the target service requires +a specific `SOAPAction` value, it is supplied in the Camel header using +the key `SOAPAction` (case-insensitive). -The Camel CXF endpoint consumer POJO data format is based on the -http://cxf.apache.org/docs/invokers.html[CXF invoker], so the -message header has a property with the name of -`CxfConstants.OPERATION_NAME` and the message body is a list of the SEI -method parameters. +[#cxf-loggingout-interceptor-in-message-mode] +==== How to enable CXF's LoggingOutInterceptor in RAW mode -Consider the https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/wsdl_first/PersonProcessor.java[PersonProcessor] example code: +CXF's `LoggingOutInterceptor` outputs outbound message that goes on the +wire to logging system (Java Util Logging). Since the +`LoggingOutInterceptor` is in `PRE_STREAM` phase (but `PRE_STREAM` phase +is removed in `RAW` mode), you have to configure +`LoggingOutInterceptor` to be run during the `WRITE` phase. The +following is an example. +[tabs] +==== +Java (Quarkus):: ++ [source,java] ---- -public class PersonProcessor implements Processor { - - private static final Logger LOG = LoggerFactory.getLogger(PersonProcessor.class); - - @Override - @SuppressWarnings("unchecked") - public void process(Exchange exchange) throws Exception { - LOG.info("processing exchange in camel"); - - BindingOperationInfo boi = (BindingOperationInfo) exchange.getProperty(BindingOperationInfo.class.getName()); - if (boi != null) { - LOG.info("boi.isUnwrapped" + boi.isUnwrapped()); - } - // Get the parameter list which element is the holder. - MessageContentsList msgList = (MessageContentsList) exchange.getIn().getBody(); - Holder<String> personId = (Holder<String>) msgList.get(0); - Holder<String> ssn = (Holder<String>) msgList.get(1); - Holder<String> name = (Holder<String>) msgList.get(2); - - if (personId.value == null || personId.value.length() == 0) { - LOG.info("person id 123, so throwing exception"); - // Try to throw out the soap fault message - org.apache.camel.wsdl_first.types.UnknownPersonFault personFault - = new org.apache.camel.wsdl_first.types.UnknownPersonFault(); - personFault.setPersonId(""); - org.apache.camel.wsdl_first.UnknownPersonFault fault - = new org.apache.camel.wsdl_first.UnknownPersonFault("Get the null value of person name", personFault); - exchange.getMessage().setBody(fault); - return; - } +import java.util.List; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.cxf.common.DataFormat; +import org.apache.camel.component.cxf.jaxws.CxfEndpoint; +import org.apache.cxf.interceptor.LoggingOutInterceptor; +import org.apache.cxf.phase.Phase; +import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; - name.value = "Bonjour"; - ssn.value = "123"; - LOG.info("setting Bonjour as the response"); - // Set the response message, the first element is the return value of the operation, - // the others are the holders of method parameters - exchange.getMessage().setBody(new Object[] { null, personId, ssn, name }); - } +... +@Produces +@SessionScoped +@Named +CxfEndpoint soapMtomEnabledServerPayloadModeEndpoint() { + final CxfEndpoint result = new CxfEndpoint(); + result.setServiceClass(HelloService.class); + result.setDataFormat(DataFormat.RAW); + result.setOutFaultInterceptors(List.of(new LoggingOutInterceptor(Phase.WRITE)));; + result.setAddress("/helloworld"); + return result; } ---- -== How to prepare the message for the Camel CXF endpoint in POJO data format +XML (Spring):: ++ +[source,xml] +---- +<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"> + <!-- it really should have been user-prestream, but CXF does have such a phase! --> + <constructor-arg value="write"/> +</bean> -The Camel CXF endpoint producer is based on the -https://github.com/apache/cxf/blob/master/core/src/main/java/org/apache/cxf/endpoint/Client.java[CXF client API]. -First, you need to specify the operation name in the message -header, then add the method parameters to a list, and initialize the -message with this parameter list. The response message's body is a -messageContentsList, you can get the result from that list. +<cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:${CXFTestSupport.port2}/LoggingInterceptorInMessageModeTest/helloworld" + serviceClass="org.apache.camel.component.cxf.HelloService"> + <cxf:outInterceptors> + <ref bean="loggingOutInterceptor"/> + </cxf:outInterceptors> + <cxf:properties> + <entry key="dataFormat" value="RAW"/> + </cxf:properties> +</cxf:cxfEndpoint> +---- +==== -If you don't specify the operation name in the message header, -`CxfProducer` will try to use the `defaultOperationName` from -`CxfEndpoint`, if there is no `defaultOperationName` set on -`CxfEndpoint`, it will pick up the first operationName from the Operation -list. +=== Description of CxfHeaderFilterStrategy options -If you want to get the object array from the message body, you can get -the body using `message.getBody(Object[].class)`, as shown in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfProducerRouterTest.java#L117[CxfProducerRouterTest.testInvokingSimpleServerWithParams]: +There are _in-band_ and _out-of-band_ on-the-wire headers from the +perspective of a JAXWS WSDL-first developer. -[source,java] ----- -Exchange senderExchange = new DefaultExchange(context, ExchangePattern.InOut); -final List<String> params = new ArrayList<>(); -// Prepare the request message for the camel-cxf procedure -params.add(TEST_MESSAGE); -senderExchange.getIn().setBody(params); -senderExchange.getIn().setHeader(CxfConstants.OPERATION_NAME, ECHO_OPERATION); +The _in-band_ headers are headers that are explicitly defined as part of +the WSDL binding contract for an endpoint such as SOAP headers. -Exchange exchange = template.send("direct:EndpointA", senderExchange); +The _out-of-band_ headers are headers that are serialized over the wire, +but are not explicitly part of the WSDL binding contract. -org.apache.camel.Message out = exchange.getMessage(); -// The response message's body is a MessageContentsList which first element is the return value of the operation, -// If there are some holder parameters, the holder parameter will be filled in the reset of List. -// The result will be extracted from the MessageContentsList with the String class type -MessageContentsList result = (MessageContentsList) out.getBody(); -LOG.info("Received output text: " + result.get(0)); -Map<String, Object> responseContext = CastUtils.cast((Map<?, ?>) out.getHeader(Client.RESPONSE_CONTEXT)); -assertNotNull(responseContext); -assertEquals("UTF-8", responseContext.get(org.apache.cxf.message.Message.ENCODING), - "We should get the response context here"); -assertEquals("echo " + TEST_MESSAGE, result.get(0), "Reply body on Camel is wrong"); ----- +Headers relaying/filtering is bi-directional. -== How to consume a message from a Camel CXF endpoint in PAYLOAD data format +When a route has a CXF endpoint and the developer needs to have +on-the-wire headers, such as SOAP headers, be relayed along the route to +be consumed say by another JAXWS endpoint, a `CxfHeaderFilterStrategy` +instance should be set on the CXF endpoint, then `relayHeaders` property +of the `CxfHeaderFilterStrategy` instance should be set to `true`, which +is the default value. Plus, the `CxfHeaderFilterStrategy` instance also +holds a list of `MessageHeaderFilter` interface, which decides if a specific +header will be relayed or not. -`PAYLOAD` means that you process the payload from the SOAP -envelope as a native CxfPayload. `Message.getBody()` will return a -`org.apache.camel.component.cxf.CxfPayload` object, with getters -for SOAP message headers and the SOAP body. +Take a look at the tests that show how you'd be able to relay/drop +headers here: -See https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerPayloadTest.java#L68[CxfConsumerPayloadTest]: +https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/soap/headers/CxfMessageHeadersRelayTest.java[CxfMessageHeadersRelayTest] -[source,java] ----- -protected RouteBuilder createRouteBuilder() { - return new RouteBuilder() { - public void configure() { - from(simpleEndpointURI + "&dataFormat=PAYLOAD").to("log:info").process(new Processor() { - @SuppressWarnings("unchecked") - public void process(final Exchange exchange) throws Exception { - CxfPayload<SoapHeader> requestPayload = exchange.getIn().getBody(CxfPayload.class); - List<Source> inElements = requestPayload.getBodySources(); - List<Source> outElements = new ArrayList<>(); - // You can use a customer toStringConverter to turn a CxfPayLoad message into String as you want - String request = exchange.getIn().getBody(String.class); - XmlConverter converter = new XmlConverter(); - String documentString = ECHO_RESPONSE; +* The `relayHeaders=true` expresses an intent to relay the headers. The +actual decision on whether a given header is relayed is delegated to a +pluggable instance that implements the `MessageHeaderFilter` interface. +A concrete implementation of `MessageHeaderFilter` will be consulted to +decide if a header needs to be relayed or not. There is already an +implementation of `SoapMessageHeaderFilter` which binds itself to +well-known SOAP name spaces. If there is a header on the wire whose name space +is unknown to the runtime, the header will be simply relayed. - Element in = new XmlConverter().toDOMElement(inElements.get(0)); - // Check the element namespace - if (!in.getNamespaceURI().equals(ELEMENT_NAMESPACE)) { - throw new IllegalArgumentException("Wrong element namespace"); - } - if (in.getLocalName().equals("echoBoolean")) { - documentString = ECHO_BOOLEAN_RESPONSE; - checkRequest("ECHO_BOOLEAN_REQUEST", request); - } else { - documentString = ECHO_RESPONSE; - checkRequest("ECHO_REQUEST", request); - } - Document outDocument = converter.toDOMDocument(documentString, exchange); - outElements.add(new DOMSource(outDocument.getDocumentElement())); - // set the payload header with null - CxfPayload<SoapHeader> responsePayload = new CxfPayload<>(null, outElements, null); - exchange.getMessage().setBody(responsePayload); - } - }); - } - }; -} ----- +* `POJO` and `PAYLOAD` modes are supported. In `POJO` mode, only +out-of-band message headers are available for filtering as the in-band +headers have been processed and removed from the header list by CXF. The +in-band headers are incorporated into the `MessageContentList` in POJO +mode. The Camel CXF component does make any attempt to remove the +in-band headers from the `MessageContentList`. If filtering of in-band +headers is required, please use `PAYLOAD` mode or plug in a (pretty +straightforward) CXF interceptor/JAXWS Handler to the CXF endpoint. +Here is an example of configuring the `CxfHeaderFilterStrategy`. -== How to get and set SOAP headers in POJO mode -`POJO` means that the data format is a _"list of Java objects"_ when the -Camel CXF endpoint produces or consumes Camel exchanges. Even though -Camel exposes the message body as POJOs in this mode, Camel CXF still -provides access to read and write SOAP headers. However, since CXF -interceptors remove in-band SOAP headers from the header list, after they -have been processed, only out-of-band SOAP headers are available to -Camel CXF in POJO mode. +[source,xml] +---- +<bean id="dropAllMessageHeadersStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy"> -The following example illustrates how to get/set SOAP headers. Suppose we -have a route that forwards from one Camel CXF endpoint to another. That -is, `SOAP Client -> Camel -> CXF service`. We can attach two processors to -obtain/insert SOAP headers at (1) before a request goes out to the CXF -service and (2) before the response comes back to the SOAP Client. Processors -(1) and (2) in this example are `InsertRequestOutHeaderProcessor` and -`InsertResponseOutHeaderProcessor`. Our route looks like this: + <!-- Set relayHeaders to false to drop all SOAP headers --> + <property name="relayHeaders" value="false"/> -[tabs] -==== -Java:: -+ -[source,java] ----- -from("cxf:bean:routerRelayEndpointWithInsertion") - .process(new InsertRequestOutHeaderProcessor()) - .to("cxf:bean:serviceRelayEndpointWithInsertion") - .process(new InsertResponseOutHeaderProcessor()); +</bean> ---- -XML:: -+ +Then, your endpoint can reference the `CxfHeaderFilterStrategy`: + [source,xml] ---- <route> - <from uri="cxf:bean:routerRelayEndpointWithInsertion"/> - <process ref="InsertRequestOutHeaderProcessor" /> - <to uri="cxf:bean:serviceRelayEndpointWithInsertion"/> - <process ref="InsertResponseOutHeaderProcessor" /> + <from uri="cxf:bean:routerNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/> + <to uri="cxf:bean:serviceNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/> </route> ---- -==== -SOAP headers are propagated to and from Camel Message headers. The Camel -message header name is `org.apache.cxf.headers.Header.list` which is a -constant defined in CXF (`org.apache.cxf.headers.Header.HEADER_LIST`). The -header value is a List of CXF `SoapHeader` objects -(`org.apache.cxf.binding.soap.SoapHeader`). -The following snippet is the `InsertResponseOutHeaderProcessor` (that inserts a new SOAP header in the response message). The way to access SOAP headers in both -`InsertResponseOutHeaderProcessor` and `InsertRequestOutHeaderProcessor` are -actually the same. -The only difference between the two processors is setting the direction of the inserted SOAP header. +* You can plug in your own `MessageHeaderFilter` implementations overriding +or adding additional ones to the list of relays. To override a +preloaded relay instance, make sure that your `MessageHeaderFilter` +implementation services the same name spaces as the one you are looking to +override. -You can find the `InsertResponseOutHeaderProcessor` example in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/soap/headers/CxfMessageHeadersRelayTest.java#L731[CxfMessageHeadersRelayTest]: +Here is an example of configuring user defined Message Header Filters: -[source,java] +[source,xml] ---- -public static class InsertResponseOutHeaderProcessor implements Processor { - - public void process(Exchange exchange) throws Exception { - List<SoapHeader> soapHeaders = CastUtils.cast((List<?>)exchange.getIn().getHeader(Header.HEADER_LIST)); - - // Insert a new header - String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><outofbandHeader " - + "xmlns=\"http://cxf.apache.org/outofband/Header\" hdrAttribute=\"testHdrAttribute\" " - + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" soap:mustUnderstand=\"1\">" - + "<name>New_testOobHeader</name><value>New_testOobHeaderValue</value></outofbandHeader>"; - SoapHeader newHeader = new SoapHeader(soapHeaders.get(0).getName(), - DOMUtils.readXml(new StringReader(xml)).getDocumentElement()); - // make sure the direction is OUT since it is a response message. - newHeader.setDirection(Direction.DIRECTION_OUT); - //newHeader.setMustUnderstand(false); - soapHeaders.add(newHeader); - - } +<bean id="customMessageFilterStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy"> + <property name="messageHeaderFilters"> + <list> + <!-- SoapMessageHeaderFilter is the built-in filter. It can be removed by omitting it. --> + <bean class="org.apache.camel.component.cxf.common.header.SoapMessageHeaderFilter"/> -} + <!-- Add custom filter here --> + <bean class="org.apache.camel.component.cxf.soap.headers.CustomHeaderFilter"/> + </list> + </property> +</bean> ---- -== How to get and set SOAP headers in PAYLOAD mode +* In addition to `relayHeaders`, the following properties can be +configured in `CxfHeaderFilterStrategy`. -We've already shown how to access the SOAP message as `CxfPayload` object in -PAYLOAD mode in the section <<How to consume a message from a Camel CXF endpoint in PAYLOAD data format>>. +[width="100%",cols="10%,10%,80%",options="header",] +|======================================================================= +|Name |Required |Description +|`relayHeaders` |No |All message headers will be processed by Message Header Filters +_Type_: `boolean` +_Default_: `true` -Once you obtain a `CxfPayload` object, you can invoke the -`CxfPayload.getHeaders()` method that returns a List of DOM Elements (SOAP -headers). +|`relayAllMessageHeaders` | No |All message headers will be propagated (without processing by Message +Header Filters) +_Type_: `boolean` +_Default_: `false` -For example, see https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfPayLoadSoapHeaderTest.java#L53[CxfPayLoadSoapHeaderTest]: +|`allowFilterNamespaceClash` |No |If two filters overlap in activation namespace, the property controls how +it should be handled. If the value is `true`, last one wins. If the +value is `false`, it will throw an exception +_Type_: `boolean` +_Default_: `false` +|======================================================================= -[source,java] ----- -from(getRouterEndpointURI()).process(new Processor() { - @SuppressWarnings("unchecked") - public void process(Exchange exchange) throws Exception { - CxfPayload<SoapHeader> payload = exchange.getIn().getBody(CxfPayload.class); - List<Source> elements = payload.getBodySources(); - assertNotNull(elements, "We should get the elements here"); - assertEquals(1, elements.size(), "Get the wrong elements size"); +=== How to make the Camel CXF component use log4j instead of java.util.logging - Element el = new XmlConverter().toDOMElement(elements.get(0)); - elements.set(0, new DOMSource(el)); - assertEquals("http://camel.apache.org/pizza/types", - el.getNamespaceURI(), "Get the wrong namespace URI"); +CXF's default logger is `java.util.logging`. If you want to change it to +log4j, proceed as follows. Create a file, in the classpath, named +`META-INF/cxf/org.apache.cxf.logger`. This file should contain the +fully qualified name of the class, +`org.apache.cxf.common.logging.Log4jLogger`, with no comments, on a +single line. - List<SoapHeader> headers = payload.getHeaders(); - assertNotNull(headers, "We should get the headers here"); - assertEquals(1, headers.size(), "Get the wrong headers size"); - assertEquals("http://camel.apache.org/pizza/types", - ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI"); - // alternatively, you can also get the SOAP header via the camel header: - headers = exchange.getIn().getHeader(Header.HEADER_LIST, List.class); - assertNotNull(headers, "We should get the headers here"); - assertEquals(1, headers.size(), "Get the wrong headers size"); - assertEquals("http://camel.apache.org/pizza/types", - ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI"); +=== How to let Camel CXF response start with xml processing instruction - } +If you are using some SOAP client such as PHP, you will get this kind of +error because CXF doesn't add the XML processing instruction +`<?xml version="1.0" encoding="utf-8"?>`: -}) -.to(getServiceEndpointURI()); +---- +Error:sendSms: SoapFault exception: [Client] looks like we got no XML document in [...] ---- -You can also use the same way as described in -subchapter "How to get and set SOAP headers in POJO mode" to set or get -the SOAP headers. So, you can use the -header `org.apache.cxf.headers.Header.list` to get and set a list of -SOAP headers.This does also mean that if you have a route that forwards -from one Camel CXF endpoint to another (`SOAP Client -> Camel -> CXF -service`), now also the SOAP headers sent by the SOAP client are -forwarded to the CXF service. If you do not want that these headers are -forwarded, you have to remove them in the Camel header -`org.apache.cxf.headers.Header.list`. - -== SOAP headers are not available in RAW mode - -SOAP headers are not available in RAW mode as SOAP processing is -skipped. - -== How to throw a SOAP Fault from Camel - -If you are using a Camel CXF endpoint to consume the SOAP request, you -may need to throw the SOAP Fault from the camel context. + - Basically, you can use the `throwFault` DSL to do that; it works for -`POJO`, `PAYLOAD` and `RAW` data format. + - You can define the soap fault as shown in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfCustomizedExceptionTest.java#L65[CxfCustomizedExceptionTest]: +To resolve this issue, you need to tell `StaxOutInterceptor` to +write the XML start document for you, as in the https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/WriteXmlDeclarationInterceptor.java[WriteXmlDeclarationInterceptor] below: [source,java] ---- -SOAP_FAULT = new SoapFault(EXCEPTION_MESSAGE, SoapFault.FAULT_CODE_CLIENT); -Element detail = SOAP_FAULT.getOrCreateDetail(); -Document doc = detail.getOwnerDocument(); -Text tn = doc.createTextNode(DETAIL_TEXT); -detail.appendChild(tn); ----- +public class WriteXmlDeclarationInterceptor extends AbstractPhaseInterceptor<SoapMessage> { + public WriteXmlDeclarationInterceptor() { + super(Phase.PRE_STREAM); + addBefore(StaxOutInterceptor.class.getName()); + } -Then throw it as you like: + public void handleMessage(SoapMessage message) throws Fault { + message.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE); + } -[source,java] ----- -from(routerEndpointURI).setFaultBody(constant(SOAP_FAULT)); +} ---- - -If your CXF endpoint is working in the `RAW` data format, you could -set the SOAP Fault message in the message body and set the response -code in the message header as demonstrated by https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfMessageStreamExceptionTest.java#L43[CxfMessageStreamExceptionTest]: +As an alternative, you can add a message header for it as demonstrated in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerTest.java#L62[CxfConsumerTest]: [source,java] ---- -from(routerEndpointURI).process(new Processor() { - - public void process(Exchange exchange) throws Exception { - Message out = exchange.getMessage(); - // Set the message body - out.setBody(this.getClass().getResourceAsStream("SoapFaultMessage.xml")); - // Set the response code here - out.setHeader(org.apache.cxf.message.Message.RESPONSE_CODE, new Integer(500)); - } - -}); + // set up the response context which force start document + Map<String, Object> map = new HashMap<String, Object>(); + map.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE); + exchange.getMessage().setHeader(Client.RESPONSE_CONTEXT, map); ---- -Same for using POJO data format. You can set the SOAPFault on the _OUT_ body. - -[#propagate-request-response-context] -== How to propagate a Camel CXF endpoint's request and response context +=== Configure the CXF endpoints with Spring -https://github.com/apache/cxf/blob/master/core/src/main/java/org/apache/cxf/endpoint/Client.java[CXF client API] provides a way to invoke the operation with request and -response context. -If you are using a Camel CXF endpoint producer to -invoke the outside web service, you can set the request context and get -response context with the following code: +You can configure the CXF endpoint with the Spring configuration file +shown below, and you can also embed the endpoint into the `camelContext` +tags. When you are invoking the service endpoint, you can set the +`operationName` and `operationNamespace` headers to explicitly state +which operation you are calling. -[source,java] +[source,xml] ---- -CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), new Processor() { - public void process(final Exchange exchange) { - final List<String> params = new ArrayList<String>(); - params.add(TEST_MESSAGE); - // Set the request context to the inMessage - Map<String, Object> requestContext = new HashMap<String, Object>(); - requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS); - exchange.getIn().setBody(params); - exchange.getIn().setHeader(Client.REQUEST_CONTEXT , requestContext); - exchange.getIn().setHeader(CxfConstants.OPERATION_NAME, GREET_ME_OPERATION); - } -}); -org.apache.camel.Message out = exchange.getMessage(); -// The output is an object array, the first element of the array is the return value -Object\[\] output = out.getBody(Object\[\].class); -LOG.info("Received output text: " + output\[0\]); -// Get the response context form outMessage -Map<String, Object> responseContext = CastUtils.cast((Map)out.getHeader(Client.RESPONSE_CONTEXT)); -assertNotNull(responseContext); -assertEquals("Get the wrong wsdl operation name", "{http://apache.org/hello_world_soap_http}greetMe", - responseContext.get("javax.xml.ws.wsdl.operation").toString()); +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:cxf="http://camel.apache.org/schema/cxf" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd + http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> + <cxf:cxfEndpoint id="routerEndpoint" address="http://localhost:9003/CamelContext/RouterPort" + serviceClass="org.apache.hello_world_soap_http.GreeterImpl"/> + <cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:9000/SoapContext/SoapPort" + wsdlURL="testutils/hello_world.wsdl" + serviceClass="org.apache.hello_world_soap_http.Greeter" + endpointName="s:SoapPort" + serviceName="s:SOAPService" + xmlns:s="http://apache.org/hello_world_soap_http" /> + <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> + <route> + <from uri="cxf:bean:routerEndpoint" /> + <to uri="cxf:bean:serviceEndpoint" /> + </route> + </camelContext> + </beans> ---- -== Attachment Support - -=== POJO Mode +Be sure to include the JAX-WS `schemaLocation` attribute specified on +the root `beans` element. This allows CXF to validate the file and is +required. Also note the namespace declarations at the end of the +`<cxf:cxfEndpoint/>` tag. These declarations are required because the combined `\{namespace}localName` syntax is presently not supported for this tag's +attribute values. -Message Transmission Optimization Mechanism (MTOM) is supported if enabled - check -the example in Payload Mode for enabling MTOM. -Since attachments are marshalled and unmarshalled into POJOs, the attachments should be -retrieved from the Apache Camel message body (as a parameter list), and it isn't -possible to retrieve attachments by Camel Message API +The `cxf:cxfEndpoint` element supports many additional attributes: -[source,java] ----- -DataHandler handler = Exchange.getIn(AttachmentMessage.class).getAttachment("id"); ----- +[width="100%",cols="50%,50%",options="header",] +|======================================================================= +|Name |Value -=== Payload Mode +|`PortName` |The endpoint name this service is implementing, it maps to the +`wsdl:port@name`. In the format of `ns:PORT_NAME` where `ns` is a +namespace prefix valid at this scope. -Message Transmission Optimization Mechanism (MTOM) is supported by this Mode. -Attachments can be retrieved by Camel Message APIs mentioned above. -SOAP with Attachment (SwA) is supported and attachments can be retrieved. -SwA is the default (same as setting the CXF endpoint property `mtomEnabled` to `false`). +|`serviceName` |The service name this service is implementing, it maps to the +`wsdl:service@name`. In the format of `ns:SERVICE_NAME` where `ns` is a +namespace prefix valid at this scope. -To enable MTOM, set the CXF endpoint property `mtomEnabled` to `true`. +|`wsdlURL` |The location of the WSDL. Can be on the classpath, file system, or be +hosted remotely. -[tabs] -==== -Java (Quarkus):: -+ -[source,java] ----- -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.cxf.common.DataFormat; -import org.apache.camel.component.cxf.jaxws.CxfEndpoint; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.SessionScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Named; +|`bindingId` |The `bindingId` for the service model to use. -@ApplicationScoped -public class CxfSoapMtomRoutes extends RouteBuilder { +|`address` |The service publish address. - @Override - public void configure() { - from("cxf:bean:mtomPayloadModeEndpoint") - .process( exchange -> { ... }); - } +|`bus` |The bus name that will be used in the JAX-WS endpoint. - @Produces - @SessionScoped - @Named - CxfEndpoint mtomPayloadModeEndpoint() { - final CxfEndpoint result = new CxfEndpoint(); - result.setServiceClass(MyMtomService.class); - result.setDataFormat(DataFormat.PAYLOAD); - result.setMtomEnabled(true); - result.setAddress("/mtom/hello"); - return result; - } -} ----- +|`serviceClass` |The class name of the SEI (Service Endpoint Interface) class which could +have JSR181 annotation or not. +|======================================================================= -XML (Spring):: -+ -[source,xml] ----- -<cxf:cxfEndpoint id="mtomPayloadModeEndpoint" address="http://localhost:${CXFTestSupport.port1}/CxfMtomRouterPayloadModeTest/mtom" - wsdlURL="mtom.wsdl" - serviceName="ns:MyMtomService" - endpointName="ns:MyMtomPort" - xmlns:ns="http://apache.org/camel/cxf/mtom_feature"> +It also supports many child elements: - <cxf:properties> - <!-- enable mtom by setting this property to true --> - <entry key="mtom-enabled" value="true"/> - <!-- set the Camel CXF endpoint data fromat to PAYLOAD mode --> - <entry key="dataFormat" value="PAYLOAD"/> - </cxf:properties> -</cxf:cxfEndpoint> ----- -==== +[width="100%",cols="50%,50%",options="header",] +|======================================================================= +|Name |Value -You can produce a Camel message with attachment to send to a CXF endpoint in Payload mode. +|`cxf:inInterceptors` |The incoming interceptors for this endpoint. A list of `<bean>` or +`<ref>`. -[source,java] ----- -Exchange exchange = context.createProducerTemplate().send("direct:testEndpoint", new Processor() { +|`cxf:inFaultInterceptors` |The incoming fault interceptors for this endpoint. A list of `<bean>` or +`<ref>`. - public void process(Exchange exchange) throws Exception { - exchange.setPattern(ExchangePattern.InOut); - List<Source> elements = new ArrayList<Source>(); - elements.add(new DOMSource(DOMUtils.readXml(new StringReader(MtomTestHelper.REQ_MESSAGE)).getDocumentElement())); - CxfPayload<SoapHeader> body = new CxfPayload<SoapHeader>(new ArrayList<SoapHeader>(), - elements, null); - exchange.getIn().setBody(body); - exchange.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_PHOTO_CID, - new DataHandler(new ByteArrayDataSource(MtomTestHelper.REQ_PHOTO_DATA, "application/octet-stream"))); +|`cxf:outInterceptors` |The outgoing interceptors for this endpoint. A list of `<bean>` or +`<ref>`. - exchange.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_IMAGE_CID, - new DataHandler(new ByteArrayDataSource(MtomTestHelper.requestJpeg, "image/jpeg"))); +|`cxf:outFaultInterceptors` |The outgoing fault interceptors for this endpoint. A list of `<bean>` or +`<ref>`. - } +|`cxf:properties` | A properties map which should be supplied to the JAX-WS endpoint. See +below. -}); +|`cxf:handlers` |A JAX-WS handler list which should be supplied to the JAX-WS endpoint. +See below. -// process response +|`cxf:dataBinding` |You can specify which `DataBinding` will be used in the endpoint. +This can be supplied using the Spring `<bean class="MyDataBinding"/>` +syntax. -CxfPayload<SoapHeader> out = exchange.getMessage().getBody(CxfPayload.class); -assertEquals(1, out.getBody().size()); +|`cxf:binding` |You can specify the `BindingFactory` for this endpoint to use. This can +be supplied using the Spring `<bean class="MyBindingFactory"/>` syntax. -Map<String, String> ns = new HashMap<>(); -ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS); -ns.put("xop", MtomTestHelper.XOP_NS); +|`cxf:features` |The features that hold the interceptors for this endpoint. A list of +beans or refs -XPathUtils xu = new XPathUtils(ns); -Element oute = new XmlConverter().toDOMElement(out.getBody().get(0)); -Element ele = (Element) xu.getValue("//ns:DetailResponse/ns:photo/xop:Include", oute, - XPathConstants.NODE); -String photoId = ele.getAttribute("href").substring(4); // skip "cid:" +|`cxf:schemaLocations` |The schema locations for endpoint to use. A list of schemaLocations -ele = (Element) xu.getValue("//ns:DetailResponse/ns:image/xop:Include", oute, - XPathConstants.NODE); -String imageId = ele.getAttribute("href").substring(4); // skip "cid:" +|`cxf:serviceFactory` |The service factory for this endpoint to use. This can be supplied using +the Spring `<bean class="MyServiceFactory"/>` syntax +|======================================================================= -DataHandler dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(photoId)); -assertEquals("application/octet-stream", dr.getContentType()); -assertArrayEquals(MtomTestHelper.RESP_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream())); +You can find more advanced examples that show how to provide +interceptors, properties and handlers on the CXF +http://cxf.apache.org/docs/jax-ws-configuration.html[JAX-WS +Configuration page]. -dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(imageId)); -assertEquals("image/jpeg", dr.getContentType()); +[NOTE] +==== +You can use `cxf:properties` to set the Camel CXF endpoint's dataFormat +and setDefaultBus properties from spring configuration file. -BufferedImage image = ImageIO.read(dr.getInputStream()); -assertEquals(560, image.getWidth()); -assertEquals(300, image.getHeight()); +[source,xml] +---- +<cxf:cxfEndpoint id="testEndpoint" address="http://localhost:9000/router" + serviceClass="org.apache.camel.component.cxf.HelloService" + endpointName="s:HelloPort" + serviceName="s:HelloService" + xmlns:s="http://www.example.com/test"> + <cxf:properties> + <entry key="dataFormat" value="RAW"/> + <entry key="setDefaultBus" value="true"/> + </cxf:properties> + </cxf:cxfEndpoint> ---- +==== + +== Examples + +=== How to create a simple CXF service with POJO data format + +Having simple java web service interface: -You can also consume a Camel message received from a CXF endpoint in Payload mode. -The https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/mtom/CxfMtomConsumerPayloadModeTest.java#L97[CxfMtomConsumerPayloadModeTest] illustrates how this works: [source,java] ---- -public static class MyProcessor implements Processor { +package org.apache.camel.component.cxf.soap.server; - @Override - @SuppressWarnings("unchecked") - public void process(Exchange exchange) throws Exception { - CxfPayload<SoapHeader> in = exchange.getIn().getBody(CxfPayload.class); +@WebService(targetNamespace = "http://server.soap.cxf.component.camel.apache.org/", name = "EchoService") +public interface EchoService { - // verify request - assertEquals(1, in.getBody().size()); + String echo(String text); +} +---- - Map<String, String> ns = new HashMap<>(); - ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS); - ns.put("xop", MtomTestHelper.XOP_NS); +And implementation: - XPathUtils xu = new XPathUtils(ns); - Element body = new XmlConverter().toDOMElement(in.getBody().get(0)); - Element ele = (Element) xu.getValue("//ns:Detail/ns:photo/xop:Include", body, - XPathConstants.NODE); - String photoId = ele.getAttribute("href").substring(4); // skip "cid:" - assertEquals(MtomTestHelper.REQ_PHOTO_CID, photoId); +[source,java] +---- - ele = (Element) xu.getValue("//ns:Detail/ns:image/xop:Include", body, - XPathConstants.NODE); - String imageId = ele.getAttribute("href").substring(4); // skip "cid:" - assertEquals(MtomTestHelper.REQ_IMAGE_CID, imageId); +package org.apache.camel.component.cxf.soap.server; - DataHandler dr = exchange.getIn(AttachmentMessage.class).getAttachment(photoId); - assertEquals("application/octet-stream", dr.getContentType()); - assertArrayEquals(MtomTestHelper.REQ_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream())); +@WebService(name = "EchoService", serviceName = "EchoService", targetNamespace = "http://server.soap.cxf.component.camel.apache.org/") +public class EchoServiceImpl implements EchoService { - dr = exchange.getIn(AttachmentMessage.class).getAttachment(imageId); - assertEquals("image/jpeg", dr.getContentType()); - assertArrayEquals(MtomTestHelper.requestJpeg, IOUtils.readBytesFromStream(dr.getInputStream())); + @Override + public String echo(String text) { + return text; + } - // create response - List<Source> elements = new ArrayList<>(); - elements.add(new DOMSource(StaxUtils.read(new StringReader(MtomTestHelper.RESP_MESSAGE)).getDocumentElement())); - CxfPayload<SoapHeader> sbody = new CxfPayload<>( - new ArrayList<SoapHeader>(), - elements, null); - exchange.getMessage().setBody(sbody); - exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_PHOTO_CID, - new DataHandler(new ByteArrayDataSource(MtomTestHelper.RESP_PHOTO_DATA, "application/octet-stream"))); +} +---- - exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_IMAGE_CID, - new DataHandler(new ByteArrayDataSource(MtomTestHelper.responseJpeg, "image/jpeg"))); +We can then create the simplest CXF service (note we didn't specify the `POJO` mode, as it is the default mode): - } -} +[source,java] +---- + from("cxf:echoServiceResponseFromImpl?serviceClass=org.apache.camel.component.cxf.soap.server.EchoServiceImpl&address=/echo-impl")// no body set here; the response comes from EchoServiceImpl + .log("${body}"); ---- -=== RAW Mode +For more complicated implementation of the service (more "Camel way"), we can set the body from the route instead: -Attachments are not supported as it does not process the message at all. +[source,java] +---- + from("cxf:echoServiceResponseFromRoute?serviceClass=org.apache.camel.component.cxf.soap.server.EchoServiceImpl&address=/echo-route") + .setBody(exchange -> exchange.getMessage().getBody(String.class) + " from Camel route"); +---- -=== CXF_MESSAGE Mode -MTOM is supported, and Attachments can be retrieved by Camel Message APIs mentioned above. Note that when receiving a multipart (i.e., MTOM) message, the default `SOAPMessag`e to `String` converter will provide the complete multipart payload on the body. -If you require just the SOAP XML as a String, you can set the message body -with `message.getSOAPPart()`, and the Camel converter can do the rest of the work -for you. +=== How to consume a message from a Camel CXF endpoint in POJO data format -== Streaming Support in PAYLOAD mode +The Camel CXF endpoint consumer POJO data format is based on the +http://cxf.apache.org/docs/invokers.html[CXF invoker], so the +message header has a property with the name of +`CxfConstants.OPERATION_NAME` and the message body is a list of the SEI +method parameters. -The Camel CXF component now supports streaming of incoming -messages when using PAYLOAD mode. Previously, the incoming messages -would have been completely DOM parsed. For large messages, this is time-consuming and uses a significant amount of memory. -The incoming messages can remain as a `javax.xml.transform.Source` while -being routed and, if nothing modifies the payload, can then be directly -streamed out to the target destination. For common "simple proxy" use -cases (example: `from("cxf:...").to("cxf:...")`), this can provide very -significant performance increases as well as significantly lowered -memory requirements. +Consider the https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/wsdl_first/PersonProcessor.java[PersonProcessor] example code: -However, there are cases where streaming may not be appropriate or -desired. Due to the streaming nature, invalid incoming XML may not be -caught until later in the processing chain. Also, certain actions may -require the message to be DOM parsed anyway (like WS-Security or message -tracing and such) in which case, the advantages of the streaming are -limited. At this point, there are two ways to control the streaming: +[source,java] +---- +public class PersonProcessor implements Processor { -* Endpoint property: you can add `allowStreaming=false` as an endpoint -property to turn the streaming on/off. + private static final Logger LOG = LoggerFactory.getLogger(PersonProcessor.class); -* Component property: the `CxfComponent` object also has an `allowStreaming` -property that can set the default for endpoints created from that -component. + @Override + @SuppressWarnings("unchecked") + public void process(Exchange exchange) throws Exception { + LOG.info("processing exchange in camel"); -Global system property: you can add a system property of -`org.apache.camel.component.cxf.streaming` to `false` to turn it off. -That sets the global default, but setting the endpoint property above -will override this value for that endpoint. + BindingOperationInfo boi = (BindingOperationInfo) exchange.getProperty(BindingOperationInfo.class.getName()); + if (boi != null) { + LOG.info("boi.isUnwrapped" + boi.isUnwrapped()); + } + // Get the parameter list which element is the holder. + MessageContentsList msgList = (MessageContentsList) exchange.getIn().getBody(); + Holder<String> personId = (Holder<String>) msgList.get(0); + Holder<String> ssn = (Holder<String>) msgList.get(1); + Holder<String> name = (Holder<String>) msgList.get(2); + + if (personId.value == null || personId.value.length() == 0) { + LOG.info("person id 123, so throwing exception"); + // Try to throw out the soap fault message + org.apache.camel.wsdl_first.types.UnknownPersonFault personFault + = new org.apache.camel.wsdl_first.types.UnknownPersonFault(); + personFault.setPersonId(""); + org.apache.camel.wsdl_first.UnknownPersonFault fault + = new org.apache.camel.wsdl_first.UnknownPersonFault("Get the null value of person name", personFault); + exchange.getMessage().setBody(fault); + return; + } -== Using the generic CXF Dispatch mode + name.value = "Bonjour"; + ssn.value = "123"; + LOG.info("setting Bonjour as the response"); + // Set the response message, the first element is the return value of the operation, + // the others are the holders of method parameters + exchange.getMessage().setBody(new Object[] { null, personId, ssn, name }); + } -The Camel CXF component supports the generic -https://cxf.apache.org/docs/jax-ws-dispatch-api.html[CXF dispatch mode] that can transport messages of arbitrary structures (i.e., not bound to a specific XML schema). -To use this mode, you omit specifying the `wsdlURL` and `serviceClass` attributes of the CXF endpoint. +} +---- + +=== How to prepare the message for the Camel CXF endpoint in POJO data format + +The Camel CXF endpoint producer is based on the +https://github.com/apache/cxf/blob/master/core/src/main/java/org/apache/cxf/endpoint/Client.java[CXF client API]. +First, you need to specify the operation name in the message +header, then add the method parameters to a list, and initialize the +message with this parameter list. The response message's body is a +messageContentsList, you can get the result from that list. + +If you don't specify the operation name in the message header, +`CxfProducer` will try to use the `defaultOperationName` from +`CxfEndpoint`, if there is no `defaultOperationName` set on +`CxfEndpoint`, it will pick up the first operationName from the Operation +list. + +If you want to get the object array from the message body, you can get +the body using `message.getBody(Object[].class)`, as shown in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfProducerRouterTest.java#L117[CxfProducerRouterTest.testInvokingSimpleServerWithParams]: -[tabs] -==== -Java (Quarkus):: -+ [source,java] ---- -import org.apache.camel.component.cxf.common.DataFormat; -import org.apache.camel.component.cxf.jaxws.CxfEndpoint; -import jakarta.enterprise.context.SessionScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Named; +Exchange senderExchange = new DefaultExchange(context, ExchangePattern.InOut); +final List<String> params = new ArrayList<>(); +// Prepare the request message for the camel-cxf procedure +params.add(TEST_MESSAGE); +senderExchange.getIn().setBody(params); +senderExchange.getIn().setHeader(CxfConstants.OPERATION_NAME, ECHO_OPERATION); -... +Exchange exchange = template.send("direct:EndpointA", senderExchange); -@Produces -@SessionScoped -@Named -CxfEndpoint dispatchEndpoint() { - final CxfEndpoint result = new CxfEndpoint(); - result.setDataFormat(DataFormat.PAYLOAD); - result.setAddress("/SoapAnyPort"); - return result; -} +org.apache.camel.Message out = exchange.getMessage(); +// The response message's body is a MessageContentsList which first element is the return value of the operation, +// If there are some holder parameters, the holder parameter will be filled in the reset of List. +// The result will be extracted from the MessageContentsList with the String class type +MessageContentsList result = (MessageContentsList) out.getBody(); +LOG.info("Received output text: " + result.get(0)); +Map<String, Object> responseContext = CastUtils.cast((Map<?, ?>) out.getHeader(Client.RESPONSE_CONTEXT)); +assertNotNull(responseContext); +assertEquals("UTF-8", responseContext.get(org.apache.cxf.message.Message.ENCODING), + "We should get the response context here"); +assertEquals("echo " + TEST_MESSAGE, result.get(0), "Reply body on Camel is wrong"); ---- -XML (Spring):: -+ -[source,xml] +=== How to consume a message from a Camel CXF endpoint in PAYLOAD data format + +`PAYLOAD` means that you process the payload from the SOAP +envelope as a native CxfPayload. `Message.getBody()` will return a +`org.apache.camel.component.cxf.CxfPayload` object, with getters +for SOAP message headers and the SOAP body. + +See https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerPayloadTest.java#L68[CxfConsumerPayloadTest]: + +[source,java] ---- -<cxf:cxfEndpoint id="dispatchEndpoint" address="http://localhost:9000/SoapContext/SoapAnyPort"> - <cxf:properties> - <entry key="dataFormat" value="PAYLOAD"/> - </cxf:properties> -</cxf:cxfEndpoint> +protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from(simpleEndpointURI + "&dataFormat=PAYLOAD").to("log:info").process(new Processor() { + @SuppressWarnings("unchecked") + public void process(final Exchange exchange) throws Exception { + CxfPayload<SoapHeader> requestPayload = exchange.getIn().getBody(CxfPayload.class); + List<Source> inElements = requestPayload.getBodySources(); + List<Source> outElements = new ArrayList<>(); + // You can use a customer toStringConverter to turn a CxfPayLoad message into String as you want + String request = exchange.getIn().getBody(String.class); + XmlConverter converter = new XmlConverter(); + String documentString = ECHO_RESPONSE; + + Element in = new XmlConverter().toDOMElement(inElements.get(0)); + // Check the element namespace + if (!in.getNamespaceURI().equals(ELEMENT_NAMESPACE)) { + throw new IllegalArgumentException("Wrong element namespace"); + } + if (in.getLocalName().equals("echoBoolean")) { + documentString = ECHO_BOOLEAN_RESPONSE; + checkRequest("ECHO_BOOLEAN_REQUEST", request); + } else { + documentString = ECHO_RESPONSE; + checkRequest("ECHO_REQUEST", request); + } + Document outDocument = converter.toDOMDocument(documentString, exchange); + outElements.add(new DOMSource(outDocument.getDocumentElement())); + // set the payload header with null + CxfPayload<SoapHeader> responsePayload = new CxfPayload<>(null, outElements, null); + exchange.getMessage().setBody(responsePayload); + } + }); + } + }; +} ---- -==== -It is noted that the default CXF dispatch client does not send a -specific `SOAPAction` header. Therefore, when the target service requires -a specific `SOAPAction` value, it is supplied in the Camel header using -the key `SOAPAction` (case-insensitive). +=== How to get and set SOAP headers in POJO mode -[#cxf-loggingout-interceptor-in-message-mode] -=== How to enable CXF's LoggingOutInterceptor in RAW mode +`POJO` means that the data format is a _"list of Java objects"_ when the +Camel CXF endpoint produces or consumes Camel exchanges. Even though +Camel exposes the message body as POJOs in this mode, Camel CXF still +provides access to read and write SOAP headers. However, since CXF +interceptors remove in-band SOAP headers from the header list, after they +have been processed, only out-of-band SOAP headers are available to +Camel CXF in POJO mode. -CXF's `LoggingOutInterceptor` outputs outbound message that goes on the -wire to logging system (Java Util Logging). Since the -`LoggingOutInterceptor` is in `PRE_STREAM` phase (but `PRE_STREAM` phase -is removed in `RAW` mode), you have to configure -`LoggingOutInterceptor` to be run during the `WRITE` phase. The -following is an example. +The following example illustrates how to get/set SOAP headers. Suppose we +have a route that forwards from one Camel CXF endpoint to another. That +is, `SOAP Client -> Camel -> CXF service`. We can attach two processors to +obtain/insert SOAP headers at (1) before a request goes out to the CXF +service and (2) before the response comes back to the SOAP Client. Processors +(1) and (2) in this example are `InsertRequestOutHeaderProcessor` and +`InsertResponseOutHeaderProcessor`. Our route looks like this: [tabs] ==== -Java (Quarkus):: +Java:: + [source,java] ---- -import java.util.List; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.cxf.common.DataFormat; -import org.apache.camel.component.cxf.jaxws.CxfEndpoint; -import org.apache.cxf.interceptor.LoggingOutInterceptor; -import org.apache.cxf.phase.Phase; -import jakarta.enterprise.context.SessionScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Named; - -... - -@Produces -@SessionScoped -@Named -CxfEndpoint soapMtomEnabledServerPayloadModeEndpoint() { - final CxfEndpoint result = new CxfEndpoint(); - result.setServiceClass(HelloService.class); - result.setDataFormat(DataFormat.RAW); - result.setOutFaultInterceptors(List.of(new LoggingOutInterceptor(Phase.WRITE)));; - result.setAddress("/helloworld"); - return result; -} +from("cxf:bean:routerRelayEndpointWithInsertion") + .process(new InsertRequestOutHeaderProcessor()) + .to("cxf:bean:serviceRelayEndpointWithInsertion") + .process(new InsertResponseOutHeaderProcessor()); ---- -XML (Spring):: +XML:: + [source,xml] ---- -<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"> - <!-- it really should have been user-prestream, but CXF does have such a phase! --> - <constructor-arg value="write"/> -</bean> - -<cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:${CXFTestSupport.port2}/LoggingInterceptorInMessageModeTest/helloworld" - serviceClass="org.apache.camel.component.cxf.HelloService"> - <cxf:outInterceptors> - <ref bean="loggingOutInterceptor"/> - </cxf:outInterceptors> - <cxf:properties> - <entry key="dataFormat" value="RAW"/> - </cxf:properties> -</cxf:cxfEndpoint> +<route> + <from uri="cxf:bean:routerRelayEndpointWithInsertion"/> + <process ref="InsertRequestOutHeaderProcessor" /> + <to uri="cxf:bean:serviceRelayEndpointWithInsertion"/> + <process ref="InsertResponseOutHeaderProcessor" /> +</route> ---- ==== -== Description of CxfHeaderFilterStrategy options +SOAP headers are propagated to and from Camel Message headers. The Camel +message header name is `org.apache.cxf.headers.Header.list` which is a +constant defined in CXF (`org.apache.cxf.headers.Header.HEADER_LIST`). The +header value is a List of CXF `SoapHeader` objects +(`org.apache.cxf.binding.soap.SoapHeader`). +The following snippet is the `InsertResponseOutHeaderProcessor` (that inserts a new SOAP header in the response message). The way to access SOAP headers in both +`InsertResponseOutHeaderProcessor` and `InsertRequestOutHeaderProcessor` are +actually the same. +The only difference between the two processors is setting the direction of the inserted SOAP header. -There are _in-band_ and _out-of-band_ on-the-wire headers from the -perspective of a JAXWS WSDL-first developer. +You can find the `InsertResponseOutHeaderProcessor` example in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/soap/headers/CxfMessageHeadersRelayTest.java#L731[CxfMessageHeadersRelayTest]: -The _in-band_ headers are headers that are explicitly defined as part of -the WSDL binding contract for an endpoint such as SOAP headers. +[source,java] +---- +public static class InsertResponseOutHeaderProcessor implements Processor { -The _out-of-band_ headers are headers that are serialized over the wire, -but are not explicitly part of the WSDL binding contract. + public void process(Exchange exchange) throws Exception { + List<SoapHeader> soapHeaders = CastUtils.cast((List<?>)exchange.getIn().getHeader(Header.HEADER_LIST)); -Headers relaying/filtering is bi-directional. + // Insert a new header + String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><outofbandHeader " + + "xmlns=\"http://cxf.apache.org/outofband/Header\" hdrAttribute=\"testHdrAttribute\" " + + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" soap:mustUnderstand=\"1\">" + + "<name>New_testOobHeader</name><value>New_testOobHeaderValue</value></outofbandHeader>"; + SoapHeader newHeader = new SoapHeader(soapHeaders.get(0).getName(), + DOMUtils.readXml(new StringReader(xml)).getDocumentElement()); + // make sure the direction is OUT since it is a response message. + newHeader.setDirection(Direction.DIRECTION_OUT); + //newHeader.setMustUnderstand(false); + soapHeaders.add(newHeader); -When a route has a CXF endpoint and the developer needs to have -on-the-wire headers, such as SOAP headers, be relayed along the route to -be consumed say by another JAXWS endpoint, a `CxfHeaderFilterStrategy` -instance should be set on the CXF endpoint, then `relayHeaders` property -of the `CxfHeaderFilterStrategy` instance should be set to `true`, which -is the default value. Plus, the `CxfHeaderFilterStrategy` instance also -holds a list of `MessageHeaderFilter` interface, which decides if a specific -header will be relayed or not. + } -Take a look at the tests that show how you'd be able to relay/drop -headers here: +} +---- -https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/soap/headers/CxfMessageHeadersRelayTest.java[CxfMessageHeadersRelayTest] +=== How to get and set SOAP headers in PAYLOAD mode -* The `relayHeaders=true` expresses an intent to relay the headers. The -actual decision on whether a given header is relayed is delegated to a -pluggable instance that implements the `MessageHeaderFilter` interface. -A concrete implementation of `MessageHeaderFilter` will be consulted to -decide if a header needs to be relayed or not. There is already an -implementation of `SoapMessageHeaderFilter` which binds itself to -well-known SOAP name spaces. If there is a header on the wire whose name space -is unknown to the runtime, the header will be simply relayed. +We've already shown how to access the SOAP message as `CxfPayload` object in +PAYLOAD mode in the section <<How to consume a message from a Camel CXF endpoint in PAYLOAD data format>>. -* `POJO` and `PAYLOAD` modes are supported. In `POJO` mode, only -out-of-band message headers are available for filtering as the in-band -headers have been processed and removed from the header list by CXF. The -in-band headers are incorporated into the `MessageContentList` in POJO -mode. The Camel CXF component does make any attempt to remove the -in-band headers from the `MessageContentList`. If filtering of in-band -headers is required, please use `PAYLOAD` mode or plug in a (pretty -straightforward) CXF interceptor/JAXWS Handler to the CXF endpoint. - Here is an example of configuring the `CxfHeaderFilterStrategy`. +Once you obtain a `CxfPayload` object, you can invoke the +`CxfPayload.getHeaders()` method that returns a List of DOM Elements (SOAP +headers). +For example, see https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfPayLoadSoapHeaderTest.java#L53[CxfPayLoadSoapHeaderTest]: -[source,xml] +[source,java] ---- -<bean id="dropAllMessageHeadersStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy"> +from(getRouterEndpointURI()).process(new Processor() { + @SuppressWarnings("unchecked") + public void process(Exchange exchange) throws Exception { + CxfPayload<SoapHeader> payload = exchange.getIn().getBody(CxfPayload.class); + List<Source> elements = payload.getBodySources(); + assertNotNull(elements, "We should get the elements here"); + assertEquals(1, elements.size(), "Get the wrong elements size"); - <!-- Set relayHeaders to false to drop all SOAP headers --> - <property name="relayHeaders" value="false"/> + Element el = new XmlConverter().toDOMElement(elements.get(0)); + elements.set(0, new DOMSource(el)); + assertEquals("http://camel.apache.org/pizza/types", + el.getNamespaceURI(), "Get the wrong namespace URI"); -</bean> ----- + List<SoapHeader> headers = payload.getHeaders(); + assertNotNull(headers, "We should get the headers here"); + assertEquals(1, headers.size(), "Get the wrong headers size"); + assertEquals("http://camel.apache.org/pizza/types", + ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI"); + // alternatively, you can also get the SOAP header via the camel header: + headers = exchange.getIn().getHeader(Header.HEADER_LIST, List.class); + assertNotNull(headers, "We should get the headers here"); + assertEquals(1, headers.size(), "Get the wrong headers size"); + assertEquals("http://camel.apache.org/pizza/types", + ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI"); -Then, your endpoint can reference the `CxfHeaderFilterStrategy`: + } -[source,xml] ----- -<route> - <from uri="cxf:bean:routerNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/> - <to uri="cxf:bean:serviceNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/> -</route> +}) +.to(getServiceEndpointURI()); ---- -* You can plug in your own `MessageHeaderFilter` implementations overriding -or adding additional ones to the list of relays. To override a -preloaded relay instance, make sure that your `MessageHeaderFilter` -implementation services the same name spaces as the one you are looking to -override. +You can also use the same way as described in +subchapter "How to get and set SOAP headers in POJO mode" to set or get +the SOAP headers. So, you can use the +header `org.apache.cxf.headers.Header.list` to get and set a list of +SOAP headers.This does also mean that if you have a route that forwards +from one Camel CXF endpoint to another (`SOAP Client -> Camel -> CXF +service`), now also the SOAP headers sent by the SOAP client are +forwarded to the CXF service. If you do not want that these headers are +forwarded, you have to remove them in the Camel header +`org.apache.cxf.headers.Header.list`. -Here is an example of configuring user defined Message Header Filters: +=== SOAP headers are not available in RAW mode -[source,xml] +SOAP headers are not available in RAW mode as SOAP processing is +skipped. + +=== How to throw a SOAP Fault from Camel + +If you are using a Camel CXF endpoint to consume the SOAP request, you +may need to throw the SOAP Fault from the camel context. + + Basically, you can use the `throwFault` DSL to do that; it works for +`POJO`, `PAYLOAD` and `RAW` data format. + + You can define the soap fault as shown in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfCustomizedExceptionTest.java#L65[CxfCustomizedExceptionTest]: + +[source,java] +---- +SOAP_FAULT = new SoapFault(EXCEPTION_MESSAGE, SoapFault.FAULT_CODE_CLIENT); +Element detail = SOAP_FAULT.getOrCreateDetail(); +Document doc = detail.getOwnerDocument(); +Text tn = doc.createTextNode(DETAIL_TEXT); +detail.appendChild(tn); ---- -<bean id="customMessageFilterStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy"> - <property name="messageHeaderFilters"> - <list> - <!-- SoapMessageHeaderFilter is the built-in filter. It can be removed by omitting it. --> - <bean class="org.apache.camel.component.cxf.common.header.SoapMessageHeaderFilter"/> - <!-- Add custom filter here --> - <bean class="org.apache.camel.component.cxf.soap.headers.CustomHeaderFilter"/> - </list> - </property> -</bean> +Then throw it as you like: + +[source,java] +---- +from(routerEndpointURI).setFaultBody(constant(SOAP_FAULT)); ---- -* In addition to `relayHeaders`, the following properties can be -configured in `CxfHeaderFilterStrategy`. -[width="100%",cols="10%,10%,80%",options="header",] -|======================================================================= -|Name |Required |Description -|`relayHeaders` |No |All message headers will be processed by Message Header Filters - _Type_: `boolean` - _Default_: `true` +If your CXF endpoint is working in the `RAW` data format, you could +set the SOAP Fault message in the message body and set the response +code in the message header as demonstrated by https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfMessageStreamExceptionTest.java#L43[CxfMessageStreamExceptionTest]: -|`relayAllMessageHeaders` | No |All message headers will be propagated (without processing by Message -Header Filters) - _Type_: `boolean` - _Default_: `false` +[source,java] +---- +from(routerEndpointURI).process(new Processor() { -|`allowFilterNamespaceClash` |No |If two filters overlap in activation namespace, the property controls how -it should be handled. If the value is `true`, last one wins. If the -value is `false`, it will throw an exception - _Type_: `boolean` - _Default_: `false` -|======================================================================= + public void process(Exchange exchange) throws Exception { + Message out = exchange.getMessage(); + // Set the message body + out.setBody(this.getClass().getResourceAsStream("SoapFaultMessage.xml")); + // Set the response code here + out.setHeader(org.apache.cxf.message.Message.RESPONSE_CODE, new Integer(500)); + } -== How to make the Camel CXF component use log4j instead of java.util.logging +}); +---- -CXF's default logger is `java.util.logging`. If you want to change it to -log4j, proceed as follows. Create a file, in the classpath, named -`META-INF/cxf/org.apache.cxf.logger`. This file should contain the -fully qualified name of the class, -`org.apache.cxf.common.logging.Log4jLogger`, with no comments, on a -single line. +Same for using POJO data format. You can set the SOAPFault on the _OUT_ body. -== How to let Camel CXF response start with xml processing instruction +[#propagate-request-response-context] +=== How to propagate a Camel CXF endpoint's request and response context -If you are using some SOAP client such as PHP, you will get this kind of -error because CXF doesn't add the XML processing instruction -`<?xml version="1.0" encoding="utf-8"?>`: +https://github.com/apache/cxf/blob/master/core/src/main/java/org/apache/cxf/endpoint/Client.java[CXF client API] provides a way to invoke the operation with request and +response context. +If you are using a Camel CXF endpoint producer to +invoke the outside web service, you can set the request context and get +response context with the following code: +[source,java] ---- -Error:sendSms: SoapFault exception: [Client] looks like we got no XML document in [...] +CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), new Processor() { + public void process(final Exchange exchange) { + final List<String> params = new ArrayList<String>(); + params.add(TEST_MESSAGE); + // Set the request context to the inMessage + Map<String, Object> requestContext = new HashMap<String, Object>(); + requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS); + exchange.getIn().setBody(params); + exchange.getIn().setHeader(Client.REQUEST_CONTEXT , requestContext); + exchange.getIn().setHeader(CxfConstants.OPERATION_NAME, GREET_ME_OPERATION); + } +}); +org.apache.camel.Message out = exchange.getMessage(); +// The output is an object array, the first element of the array is the return value +Object\[\] output = out.getBody(Object\[\].class); +LOG.info("Received output text: " + output\[0\]); +// Get the response context form outMessage +Map<String, Object> responseContext = CastUtils.cast((Map)out.getHeader(Client.RESPONSE_CONTEXT)); +assertNotNull(responseContext); +assertEquals("Get the wrong wsdl operation name", "{http://apache.org/hello_world_soap_http}greetMe", + responseContext.get("javax.xml.ws.wsdl.operation").toString()); ---- -To resolve this issue, you need to tell `StaxOutInterceptor` to -write the XML start document for you, as in the https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/WriteXmlDeclarationInterceptor.java[WriteXmlDeclarationInterceptor] below: +=== Attachment Support + +=== POJO Mode + +Message Transmission Optimization Mechanism (MTOM) is supported if enabled - check +the example in Payload Mode for enabling MTOM. +Since attachments are marshalled and unmarshalled into POJOs, the attachments should be +retrieved from the Apache Camel message body (as a parameter list), and it isn't +possible to retrieve attachments by Camel Message API [source,java] ---- -public class WriteXmlDeclarationInterceptor extends AbstractPhaseInterceptor<SoapMessage> { - public WriteXmlDeclarationInterceptor() { - super(Phase.PRE_STREAM); - addBefore(StaxOutInterceptor.class.getName()); - } +DataHandler handler = Exchange.getIn(AttachmentMessage.class).getAttachment("id"); +---- - public void handleMessage(SoapMessage message) throws Fault { - message.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE); - } +=== Payload Mode -} ----- +Message Transmission Optimization Mechanism (MTOM) is supported by this Mode. +Attachments can be retrieved by Camel Message APIs mentioned above. +SOAP with Attachment (SwA) is supported and attachments can be retrieved. +SwA is the default (same as setting the CXF endpoint property `mtomEnabled` to `false`). -As an alternative, you can add a message header for it as demonstrated in https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerTest.java#L62[CxfConsumerTest]: +To enable MTOM, set the CXF endpoint property `mtomEnabled` to `true`. +[tabs] +==== +Java (Quarkus):: ++ [source,java] ---- - // set up the response context which force start document - Map<String, Object> map = new HashMap<String, Object>(); - map.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE); - exchange.getMessage().setHeader(Client.RESPONSE_CONTEXT, map); ----- +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.cxf.common.DataFormat; +import org.apache.camel.component.cxf.jaxws.CxfEndpoint; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; -== Configure the CXF endpoints with Spring +@ApplicationScoped +public class CxfSoapMtomRoutes extends RouteBuilder { -You can configure the CXF endpoint with the Spring configuration file -shown below, and you can also embed the endpoint into the `camelContext` -tags. When you are invoking the service endpoint, you can set the -`operationName` and `operationNamespace` headers to explicitly state -which operation you are calling. + @Override + public void configure() { + from("cxf:bean:mtomPayloadModeEndpoint") + .process( exchange -> { ... }); + } + @Produces + @SessionScoped + @Named + CxfEndpoint mtomPayloadModeEndpoint() { + final CxfEndpoint result = new CxfEndpoint(); + result.setServiceClass(MyMtomService.class); + result.setDataFormat(DataFormat.PAYLOAD); + result.setMtomEnabled(true); + result.setAddress("/mtom/hello"); + return result; + } +} +---- + +XML (Spring):: ++ [source,xml] ---- -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:cxf="http://camel.apache.org/schema/cxf" - xsi:schemaLocation=" - http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd - http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd - http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> - <cxf:cxfEndpoint id="routerEndpoint" address="http://localhost:9003/CamelContext/RouterPort" - serviceClass="org.apache.hello_world_soap_http.GreeterImpl"/> - <cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:9000/SoapContext/SoapPort" - wsdlURL="testutils/hello_world.wsdl" - serviceClass="org.apache.hello_world_soap_http.Greeter" - endpointName="s:SoapPort" - serviceName="s:SOAPService" - xmlns:s="http://apache.org/hello_world_soap_http" /> - <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> - <route> - <from uri="cxf:bean:routerEndpoint" /> - <to uri="cxf:bean:serviceEndpoint" /> - </route> - </camelContext> - </beans> +<cxf:cxfEndpoint id="mtomPayloadModeEndpoint" address="http://localhost:${CXFTestSupport.port1}/CxfMtomRouterPayloadModeTest/mtom" + wsdlURL="mtom.wsdl" + serviceName="ns:MyMtomService" + endpointName="ns:MyMtomPort" + xmlns:ns="http://apache.org/camel/cxf/mtom_feature"> + + <cxf:properties> + <!-- enable mtom by setting this property to true --> + <entry key="mtom-enabled" value="true"/> + <!-- set the Camel CXF endpoint data fromat to PAYLOAD mode --> + <entry key="dataFormat" value="PAYLOAD"/> + </cxf:properties> +</cxf:cxfEndpoint> ---- +==== -Be sure to include the JAX-WS `schemaLocation` attribute specified on -the root `beans` element. This allows CXF to validate the file and is -required. Also note the namespace declarations at the end of the -`<cxf:cxfEndpoint/>` tag. These declarations are required because the combined `\{namespace}localName` syntax is presently not supported for this tag's -attribute values. +You can produce a Camel message with attachment to send to a CXF endpoint in Payload mode. -The `cxf:cxfEndpoint` element supports many additional attributes: +[source,java] +---- +Exchange exchange = context.createProducerTemplate().send("direct:testEndpoint", new Processor() { -[width="100%",cols="50%,50%",options="header",] -|======================================================================= -|Name |Value + public void process(Exchange exchange) throws Exception { + exchange.setPattern(ExchangePattern.InOut); + List<Source> elements = new ArrayList<Source>(); + elements.add(new DOMSource(DOMUtils.readXml(new StringReader(MtomTestHelper.REQ_MESSAGE)).getDocumentElement())); + CxfPayload<SoapHeader> body = new CxfPayload<SoapHeader>(new ArrayList<SoapHeader>(), + elements, null); + exchange.getIn().setBody(body); + exchange.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_PHOTO_CID, + new DataHandler(new ByteArrayDataSource(MtomTestHelper.REQ_PHOTO_DATA, "application/octet-stream"))); -|`PortName` |The endpoint name this service is implementing, it maps to the -`wsdl:port@name`. In the format of `ns:PORT_NAME` where `ns` is a -namespace prefix valid at this scope. + exchange.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_IMAGE_CID, + new DataHandler(new ByteArrayDataSource(MtomTestHelper.requestJpeg, "image/jpeg"))); -|`serviceName` |The service name this service is implementing, it maps to the -`wsdl:service@name`. In the format of `ns:SERVICE_NAME` where `ns` is a -namespace prefix valid at this scope. + } -|`wsdlURL` |The location of the WSDL. Can be on the classpath, file system, or be -hosted remotely. +}); -|`bindingId` |The `bindingId` for the service model to use. +// process response -|`address` |The service publish address. +CxfPayload<SoapHeader> out = exchange.getMessage().getBody(CxfPayload.class); +assertEquals(1, out.getBody().size()); -|`bus` |The bus name that will be used in the JAX-WS endpoint. +Map<String, String> ns = new HashMap<>(); +ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS); +ns.put("xop", MtomTestHelper.XOP_NS); -|`serviceClass` |The class name of the SEI (Service Endpoint Interface) class which could -have JSR181 annotation or not. -|======================================================================= +XPathUtils xu = new XPathUtils(ns); +Element oute = new XmlConverter().toDOMElement(out.getBody().get(0)); +Element ele = (Element) xu.getValue("//ns:DetailResponse/ns:photo/xop:Include", oute, + XPathConstants.NODE); +String photoId = ele.getAttribute("href").substring(4); // skip "cid:" -It also supports many child elements: +ele = (Element) xu.getValue("//ns:DetailResponse/ns:image/xop:Include", oute, + XPathConstants.NODE); +String imageId = ele.getAttribute("href").substring(4); // skip "cid:" -[width="100%",cols="50%,50%",options="header",] -|======================================================================= -|Name |Value +DataHandler dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(photoId)); +assertEquals("application/octet-stream", dr.getContentType()); +assertArrayEquals(MtomTestHelper.RESP_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream())); -|`cxf:inInterceptors` |The incoming interceptors for this endpoint. A list of `<bean>` or -`<ref>`. +dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(imageId)); +assertEquals("image/jpeg", dr.getContentType()); -|`cxf:inFaultInterceptors` |The incoming fault interceptors for this endpoint. A list of `<bean>` or -`<ref>`. +BufferedImage image = ImageIO.read(dr.getInputStream()); +assertEquals(560, image.getWidth()); +assertEquals(300, image.getHeight()); +---- -|`cxf:outInterceptors` |The outgoing interceptors for this endpoint. A list of `<bean>` or -`<ref>`. +You can also consume a Camel message received from a CXF endpoint in Payload mode. +The https://github.com/apache/camel/blob/main/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/mtom/CxfMtomConsumerPayloadModeTest.java#L97[CxfMtomConsumerPayloadModeTest] illustrates how this works: -|`cxf:outFaultInterceptors` |The outgoing fault interceptors for this endpoint. A list of `<bean>` or -`<ref>`. +[source,java] +---- +public static class MyProcessor implements Processor { -|`cxf:properties` | A properties map which should be supplied to the JAX-WS endpoint. See -below. + @Override + @SuppressWarnings("unchecked") + public void process(Exchange exchange) throws Exception { + CxfPayload<SoapHeader> in = exchange.getIn().getBody(CxfPayload.class); -|`cxf:handlers` |A JAX-WS handler list which should be supplied to the JAX-WS endpoint. -See below. + // verify request + assertEquals(1, in.getBody().size()); -|`cxf:dataBinding` |You can specify which `DataBinding` will be used in the endpoint. -This can be supplied using the Spring `<bean class="MyDataBinding"/>` -syntax. + Map<String, String> ns = new HashMap<>(); + ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS); + ns.put("xop", MtomTestHelper.XOP_NS); -|`cxf:binding` |You can specify the `BindingFactory` for this endpoint to use. This can -be supplied using the Spring `<bean class="MyBindingFactory"/>` syntax. + XPathUtils xu = new XPathUtils(ns); + Element body = new XmlConverter().toDOMElement(in.getBody().get(0)); + Element ele = (Element) xu.getValue("//ns:Detail/ns:photo/xop:Include", body, + XPathConstants.NODE); + String photoId = ele.getAttribute("href").substring(4); // skip "cid:" + assertEquals(MtomTestHelper.REQ_PHOTO_CID, photoId); -|`cxf:features` |The features that hold the interceptors for this endpoint. A list of -beans or refs + ele = (Element) xu.getValue("//ns:Detail/ns:image/xop:Include", body, + XPathConstants.NODE); + String imageId = ele.getAttribute("href").substring(4); // skip "cid:" + assertEquals(MtomTestHelper.REQ_IMAGE_CID, imageId); -|`cxf:schemaLocations` |The schema locations for endpoint to use. A list of schemaLocations + DataHandler dr = exchange.getIn(AttachmentMessage.class).getAttachment(photoId); + assertEquals("application/octet-stream", dr.getContentType()); + assertArrayEquals(MtomTestHelper.REQ_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream())); -|`cxf:serviceFactory` |The service factory for this endpoint to use. This can be supplied using -the Spring `<bean class="MyServiceFactory"/>` syntax -|======================================================================= + dr = exchange.getIn(AttachmentMessage.class).getAttachment(imageId); + assertEquals("image/jpeg", dr.getContentType()); + assertArrayEquals(MtomTestHelper.requestJpeg, IOUtils.readBytesFromStream(dr.getInputStream())); -You can find more advanced examples that show how to provide -interceptors, properties and handlers on the CXF -http://cxf.apache.org/docs/jax-ws-configuration.html[JAX-WS -Configuration page]. + // create response + List<Source> elements = new ArrayList<>(); + elements.add(new DOMSource(StaxUtils.read(new StringReader(MtomTestHelper.RESP_MESSAGE)).getDocumentElement())); + CxfPayload<SoapHeader> sbody = new CxfPayload<>( + new ArrayList<SoapHeader>(), + elements, null); + exchange.getMessage().setBody(sbody); + exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_PHOTO_CID, + new DataHandler(new ByteArrayDataSource(MtomTestHelper.RESP_PHOTO_DATA, "application/octet-stream"))); -[NOTE] -==== -You can use `cxf:properties` to set the Camel CXF endpoint's dataFormat -and setDefaultBus properties from spring configuration file. + exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_IMAGE_CID, + new DataHandler(new ByteArrayDataSource(MtomTestHelper.responseJpeg, "image/jpeg"))); -[source,xml] ----- -<cxf:cxfEndpoint id="testEndpoint" address="http://localhost:9000/router" - serviceClass="org.apache.camel.component.cxf.HelloService" - endpointName="s:HelloPort" - serviceName="s:HelloService" - xmlns:s="http://www.example.com/test"> - <cxf:properties> - <entry key="dataFormat" value="RAW"/> - <entry key="setDefaultBus" value="true"/> - </cxf:properties> - </cxf:cxfEndpoint> + } +} ---- -==== + + include::spring-boot:partial$starter.adoc[] diff --git a/components/camel-dataset/src/main/docs/dataset-component.adoc b/components/camel-dataset/src/main/docs/dataset-component.adoc index 2bb2d3ebf6f..266f527721d 100644 --- a/components/camel-dataset/src/main/docs/dataset-component.adoc +++ b/components/camel-dataset/src/main/docs/dataset-component.adoc @@ -65,7 +65,9 @@ include::partial$component-endpoint-options.adoc[] include::partial$component-endpoint-headers.adoc[] // component headers: END -== Configuring DataSet +== Usage + +=== Configuring DataSet Camel will look up in the Registry for a bean implementing the `DataSet` interface. So you can register your own data set as: @@ -77,34 +79,12 @@ So you can register your own data set as: </bean> ---- -== Example - -For example, to test that a set of messages are sent to a queue and then -consumed from the queue without losing any messages: - -[source,java] ----- -// send the dataset to a queue -from("dataset:foo").to("activemq:SomeQueue"); - -// now lets test that the messages are consumed correctly -from("activemq:SomeQueue").to("dataset:foo"); ----- - -The above would look in the Registry to find the -`foo` `DataSet` instance which is used to create the messages. - -Then you create a `DataSet` implementation, such as using the -`SimpleDataSet` as described below, configuring things like how big the -data set is and what the messages look like etc. - - -== DataSetSupport (abstract class) +=== DataSetSupport (abstract class) The `DataSetSupport` abstract class is a nice starting point for new data set, and provides some useful features to derived classes. -=== Properties on DataSetSupport +==== Additional Properties on DataSetSupport [width="100%",cols="10%,10%,10%,70%",options="header",] |=== @@ -124,11 +104,11 @@ Useful for showing the progress of a large load test. If smaller than zero (` < `size` / 5, if is 0 then `size`, else set to `reportCount` value. |=== -== SimpleDataSet +=== SimpleDataSet The `SimpleDataSet` extends `DataSetSupport`, and adds a default body. -=== Additional Properties on SimpleDataSet +==== Additional Properties on SimpleDataSet [width="100%",cols="10%,10%,10%,70%",options="header",] |=== @@ -141,12 +121,12 @@ configure the `SimpleDataSet` to use it by setting the `outputTransformer` property. |=== -== ListDataSet +=== ListDataSet The List`DataSet` extends `DataSetSupport`, and adds a list of default bodies. -=== Additional Properties on ListDataSet +==== Additional Properties on ListDataSet [width="100%",cols="10%,10%,10%,70%",options="header",] |=== @@ -168,12 +148,12 @@ the payload for the exchange will be selected using the modulus of the `CamelDataSetIndex % defaultBodies.size()` ) |=== -== FileDataSet +=== FileDataSet The `FileDataSet` extends `ListDataSet`, and adds support for loading the bodies from a file. -=== Additional Properties on FileDataSet +==== Additional Properties on FileDataSet [width="100%",cols="10%,10%,10%,70%",options="header",] |=== @@ -185,5 +165,25 @@ the bodies from a file. the file into multiple payloads. |=== +== Example + +For example, to test that a set of messages are sent to a queue and then +consumed from the queue without losing any messages: + +[source,java] +---- +// send the dataset to a queue +from("dataset:foo").to("activemq:SomeQueue"); + +// now lets test that the messages are consumed correctly +from("activemq:SomeQueue").to("dataset:foo"); +---- + +The above would look in the Registry to find the +`foo` `DataSet` instance which is used to create the messages. + +Then you create a `DataSet` implementation, such as using the +`SimpleDataSet` as described below, configuring things like how big the +data set is and what the messages look like etc. include::spring-boot:partial$starter.adoc[] diff --git a/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc b/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc index 1011870fae7..27a3f74305a 100644 --- a/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc +++ b/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc @@ -33,69 +33,9 @@ datasonnet("someDSExpression"); include::partial$language-options.adoc[] // language options: END -== Example +== Usage -Here is a simple example using a DataSonnet expression as a predicate in a Message Filter: - -[tabs] -==== -Java:: -+ -[source,java] ------------------------------------------------------------------------------------------------- -// let's route if a line item is over $100 -from("queue:foo") - .filter(datasonnet("ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null")) - .to("queue:bar"); ------------------------------------------------------------------------------------------------- - -XML:: -+ -[source,xml] ------------------------------------------------------------------------------ -<route> - <from uri="queue:foo"/> - <filter> - <datasonnet>ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null</datasonnet> - <to uri="queue:bar"/> - </filter> -</route> ------------------------------------------------------------------------------ -==== - - -Here is an example of a simple DataSonnet expression as a transformation EIP. This example will transform an XML body with -`lineItems` into JSON while filtering out lines that are under 100. - -[tabs] -==== -Java:: -+ -[source,java] ------------------------------------------------------------------------------------------------- -from("queue:foo") - .transform(datasonnet("ds.filter(body.lineItems, function(item) item > 100)", String.class, "application/xml", "application/json")) - .to("queue:bar"); ------------------------------------------------------------------------------------------------- - -XML:: -+ -[source,xml] ------------------------------------------------------------------------------ -<route> - <from uri="queue:foo"/> - <filter> - <datasonnet bodyMediaType="application/xml" outputMediaType="application/json" resultTypeName="java.lang.String" > - ds.filter(body.lineItems, function(item) item > 100) - </datasonnet> - <to uri="queue:bar"/> - </filter> -</route> ------------------------------------------------------------------------------ -==== - - -== Setting a result type +=== Setting a result type The xref:datasonnet-language.adoc[DataSonnet] expression will return a `com.datasonnet.document.Document` by default. The document preserves the content type metadata along with the contents of the transformation result. In predicates, @@ -132,7 +72,7 @@ or `Map.class`, respectively. However, you must also set the output media type t NOTE: The default `Document` object is useful in situations where there are intermediate transformation steps, and so retaining the content metadata through a route execution is valuable. -== Specifying Media Types +=== Specifying Media Types Traditionally, the input and output media types are specified through the https://datasonnet.s3-us-west-2.amazonaws.com/docs-ci/primary/master/datasonnet/1.0-SNAPSHOT/headers.html[DataSonnet Header]. @@ -156,7 +96,7 @@ And for output media type: 4. The DataSonnet Header output media type directive 5. `application/x-java-object` -== Functions +=== Functions Camel adds the following DataSonnet functions that can be used to access the exchange: @@ -202,7 +142,7 @@ XML:: ----------------------------------------------------------------------------- ==== -== Loading script from external resource +=== Loading script from external resource You can externalize the script and have Apache Camel load it from a resource such as `"classpath:"`, `"file:"`, or `"http:"`. + @@ -214,6 +154,69 @@ e.g., to refer to a file on the classpath you can do: .setHeader("myHeader").datasonnet("resource:classpath:mydatasonnet.ds"); ------------------------------------------------------------------- +== Examples + +Here is a simple example using a DataSonnet expression as a predicate in a Message Filter: + +[tabs] +==== +Java:: ++ +[source,java] +------------------------------------------------------------------------------------------------ +// let's route if a line item is over $100 +from("queue:foo") + .filter(datasonnet("ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null")) + .to("queue:bar"); +------------------------------------------------------------------------------------------------ + +XML:: ++ +[source,xml] +----------------------------------------------------------------------------- +<route> + <from uri="queue:foo"/> + <filter> + <datasonnet>ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null</datasonnet> + <to uri="queue:bar"/> + </filter> +</route> +----------------------------------------------------------------------------- +==== + + +Here is an example of a simple DataSonnet expression as a transformation EIP. This example will transform an XML body with +`lineItems` into JSON while filtering out lines that are under 100. + +[tabs] +==== +Java:: ++ +[source,java] +------------------------------------------------------------------------------------------------ +from("queue:foo") + .transform(datasonnet("ds.filter(body.lineItems, function(item) item > 100)", String.class, "application/xml", "application/json")) + .to("queue:bar"); +------------------------------------------------------------------------------------------------ + +XML:: ++ +[source,xml] +----------------------------------------------------------------------------- +<route> + <from uri="queue:foo"/> + <filter> + <datasonnet bodyMediaType="application/xml" outputMediaType="application/json" resultTypeName="java.lang.String" > + ds.filter(body.lineItems, function(item) item > 100) + </datasonnet> + <to uri="queue:bar"/> + </filter> +</route> +----------------------------------------------------------------------------- +==== + + + == Dependencies To use scripting languages in your camel routes, you need to add a