Chapter 31. CXFRS

CXFRS Component

The cxfrs: component provides integration with Apache CXF for connecting to JAX-RS services hosted in CXF.
Maven users will need to add the following dependency to their pom.xml for this component:
<dependency>
   <groupId>org.apache.camel</groupId>
   <artifactId>camel-cxf</artifactId>
   <version>x.x.x</version>  <!-- use the same version as your Camel core version -->
</dependency>
Note
When using CXF as a consumer, the CXF Bean Component allows you to factor out how message payloads are received from their processing as a RESTful or SOAP web service. This has the potential of using a multitude of transports to consume web services. The bean component's configuration is also simpler and provides the fastest method to implement web services using Camel and CXF.

URI format

cxfrs://address?options
Where address represents the CXF endpoint's address
cxfrs:bean:rsEndpoint
Where rsEndpoint represents the Spring bean's name which represents the CXFRS client or server
For either style above, you can append options to the URI as follows:
cxfrs:bean:cxfEndpoint?resourceClasses=org.apache.camel.rs.Example

Options

Name Description Example Required? default value
resourceClasses The resource classes which you want to export as REST service. Multiple classes can be separated by a comma.
resourceClasses=org.apache.camel.rs.Example1,org.apache.camel.rs.Exchange2
No None
httpClientAPI New to Apache Camel 2.1 If true, the CxfRsProducer will use the HttpClientAPI to invoke the service httpClientAPI=true No true
synchronous New in 2.5, this option will let CxfRsConsumer decide to use sync or async API to do the underlying work. The default value is false which means it will try to use async API by default. synchronous=true No false
throwExceptionOnFailure New in 2.6, this option tells the CxfRsProducer to inspect return codes and will generate an Exception if the return code is larger than 207. throwExceptionOnFailure=true No true
maxClientCacheSize New in 2.6, you can set the In message header, CamelDestinationOverrideUrl, to dynamically override the target destination Web Service or REST Service defined in your routes. The implementation caches CXF clients or ClientFactoryBean in CxfProvider and CxfRsProvider. This option allows you to configure the maximum size of the cache. maxClientCacheSize=5 No 10
setDefaultBus Deprecated: Use defaultBus option from Camel 2.16 onwards. Will set the default bus when CXF endpoint create a bus by itself setDefaultBus=true No false
defaultBus Will set the default bus when CXF endpoint create a bus by itself defaultBus=true No false
bus New in 2.9.0. A default bus created by CXF Bus Factory. Use \# notation to reference a bus object from the registry. The referenced object must be an instance of org.apache.cxf.Bus. bus=#busName No None
bindingStyle
As of 2.11. Sets how requests and responses will be mapped to/from Camel. Two values are possible:
  • Default => the default style. For consumers this passes on a MessageContentsList to the route, requiring low-level processing in the route.
  • Custom => allows you to specify a custom binding through the binding option.
bindingStyle=SimpleConsumer No Default
binding
Allows you to specify a custom CxfRsBinding implementation to perform low-level processing of the raw CXF request and response objects. The implementation must be bound in the Camel registry, and you must use the hash (#) notation to refer to it.
binding=#myBinding
No
DefaultCxfRsBinding
providers
Since Camel 2.12.2 set custom JAX-RS providers list to the CxfRs endpoint.
providers=#MyProviders
No
None
schemaLocations
Since Camel 2.12.2 Sets the locations of the schemas which can be used to validate the incoming XML or JAXB-driven JSON.
schemaLocations=#MySchemaLocations
No
None
features
Since Camel 2.12.3 Set the feature list to the CxfRs endpoint.
features=#MyFeatures
No
None
properties
Since Camel 2.12.4 Set the properties to the CxfRs endpoint.
properties=#MyProperties
No
None
inInterceptors
Since Camel 2.12.4 Set the inInterceptors to the CxfRs endpoint.
inInterceptors=#MyInterceptors
No
None
outInterceptors
Since Camel 2.12.4 Set the outInterceptor to the CxfRs endpoint.
outInterceptors=#MyInterceptors
No
None
inFaultInterceptors
Since Camel 2.12.4 Set the inFaultInterceptors to the CxfRs endpoint.
inFaultInterceptors=#MyInterceptors
No
None
outFaultIntercetpros
Since Camel 2.12.4 Set the outFaultInterceptors to the CxfRs endpoint.
outFaultInterceptors=#MyInterceptors
No
None
continuationTimeout
Since Camel 2.14.0 This option is used to set the CXF continuation timeout which could be used in CxfConsumer by default when the CXF server is using Jetty or Servlet transport. (Before Camel 2.14.0, CxfConsumer just set the continuation timeout to be 0, which means the continuation suspend operation never timeout.)
continuationTimeout=800000
No
30000
ignoreDeleteMethodMessageBody
Since Camel 2.14.1 This option is used to tell CxfRsProducer to ignore the message body of the DELETE method when using HTTP API.
ignoreDeleteMethodMessageBody=true
No
false
modelRef
Since Camel 2.14.2 This option is used to specify the model file which is useful for the resource class without annotation.
Since Camel 2.15 This option can point to a model file without specifying a service class for emulating document-only endpoints.
modelRef=classpath:/CustomerServiceModel.xml
No
None
performInvocation
Since Camel 2.15 When the option is true, camel will perform the invocation of the resource class instance and put the response object into the exchange for further processing.
performInvocation=true
No
false
loggingFeatureEnabled This option enables CXF logging feature which writes inbound and outbound REST messages to log - No false
skipFaultLogging This option controls whether the PhaseInterceptorChain skips logging the Fault that it catches - No false
loggingSizeLimit When you enable the logging feature, this option is used to limit the total size of number of bytes the logger will output - No 0
propagateContexts
Since Camel 2.15 When true, JAXRS UriInfo, HttpHeaders, Request and SecurityContext contexts will be available to custom CXFRS processors as typed Camel exchange properties. These contexts can be used to analyze the current requests using JAX-RS API.
loggingFeatureEnabled
This option enables CXF Logging Feature which writes inbound and outbound REST messages to log.
No
false
skipFaultLogging
This option controls whether the PhaseInterceptorChain skips logging the Fault that it catches.
No
false
loggingSizeLimit
To limit the total size of number of bytes the logger will output when logging feature has been enabled.
No
0
You can also configure the CXF REST endpoint through the Spring configuration. Since there are lots of differences between the CXF REST client and CXF REST Server, we provide different configurations for them. Please check out the schema file and the CXF JAX-RS documentation for more information.

How to configure the REST endpoint in Apache Camel

In camel-cxf schema file, there are two elements for the REST endpoint definition. cxf:rsServer for REST consumer, cxf:rsClient for REST producer. You can find a Apache Camel REST service route configuration example here.
<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"
       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
       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://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
    ">
  <!-- Defined the real JAXRS back end service  -->
  <jaxrs:server id="restService"
		        address="http://localhost:9002/rest" 
		        staticSubresourceResolution="true">
    <jaxrs:serviceBeans>
      <ref bean="customerService"/>
    </jaxrs:serviceBeans>       
  </jaxrs:server>
  
  <!--bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider"/-->

  <bean id="customerService" class="org.apache.camel.component.cxf.jaxrs.testbean.CustomerService" />
   
  <!-- Defined the server endpoint to create the cxf-rs consumer --> 
  <cxf:rsServer id="rsServer" address="http://localhost:9000/route"
    serviceClass="org.apache.camel.component.cxf.jaxrs.testbean.CustomerService" 
      loggingFeatureEnabled="true" loggingSizeLimit="20" skipFaultLogging="true"/>

  <!-- Defined the client endpoint to create the cxf-rs consumer -->
  <cxf:rsClient id="rsClient" address="http://localhost:9002/rest"
    serviceClass="org.apache.camel.component.cxf.jaxrs.testbean.CustomerService"
      loggingFeatureEnabled="true" skipFaultLogging="true"/>
  
  <!-- The camel route context -->
  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <route>
       <from uri="cxfrs://bean://rsServer"/>
       <!-- We can remove this configure as the CXFRS producer is using the HttpAPI by default -->
       <setHeader headerName="CamelCxfRsUsingHttpAPI">
         <constant>True</constant>        
       </setHeader>
       <to uri="cxfrs://bean://rsClient"/>
    </route>
  </camelContext>
  
</beans>

How to override the CXF producer address from message header

The camel-cxfrs producer supports to override the services address by setting the message with the key of "CamelDestinationOverrideUrl".
// set up the service address from the message header to override the setting of CXF endpoint
exchange.getIn().setHeader(Exchange.DESTINATION_OVERRIDE_URL, constant(getServiceAddress()));

Consuming a REST Request - Simple Binding Style

Available as of Camel 2.11
The Default binding style is rather low-level, requiring the user to manually process the MessageContentsList object coming into the route. Thus, it tightly couples the route logic with the method signature and parameter indices of the JAX-RS operation. Somewhat inelegant, difficult and error-prone.
In contrast, the SimpleConsumer binding style performs the following mappings, in order to make the request data more accessible to you within the Camel Message:
  • JAX-RS Parameters (@HeaderParam, @QueryParam, etc.) are injected as IN message headers. The header name matches the value of the annotation.
  • The request entity (POJO or other type) becomes the IN message body. If a single entity cannot be identified in the JAX-RS method signature, it falls back to the original MessageContentsList.
  • Binary @Multipart body parts become IN message attachments, supporting DataHandler, InputStream, DataSource and CXF's Attachment class.
  • Non-binary @Multipart body parts are mapped as IN message headers. The header name matches the Body Part name.
Additionally, the following rules apply to the Response mapping:
  • If the message body type is different to javax.ws.rs.core.Response (user-built response), a new Response is created and the message body is set as the entity (so long it's not null). The response status code is taken from the Exchange.HTTP_RESPONSE_CODE header, or defaults to 200 OK if not present.
  • If the message body type is equal to javax.ws.rs.core.Response, it means that the user has built a custom response, and therefore it is respected and it becomes the final response.
  • In all cases, Camel headers permitted by custom or default HeaderFilterStrategy are added to the HTTP response.

Enabling the Simple Binding Style

This binding style can be activated by setting the bindingStyle parameter in the consumer endpoint to value SimpleConsumer:
  from("cxfrs:bean:rsServer?bindingStyle=SimpleConsumer")
    .to("log:TEST?showAll=true");

Examples of request binding with different method signatures

Below is a list of method signatures along with the expected result from the Simple binding.
public Response doAction(BusinessObject request); Request payload is placed in IN message body, replacing the original MessageContentsList.
public Response doAction(BusinessObject request, @HeaderParam("abcd") String abcd, @QueryParam("defg") String defg); Request payload placed in IN message body, replacing the original MessageContentsList. Both request params mapped as IN message headers with names abcd and defg.
public Response doAction(@HeaderParam("abcd") String abcd, @QueryParam("defg") String defg); Both request params mapped as IN message headers with names abcd and defg. The original MessageContentsList is preserved, even though it only contains the 2 parameters.
public Response doAction(@Multipart(value="body1") BusinessObject request, @Multipart(value="body2") BusinessObject request2); The first parameter is transferred as a header with name body1, and the second one is mapped as header body2. The original MessageContentsList is preserved as the IN message body.
public Response doAction(InputStream abcd); The InputStream is unwrapped from the MessageContentsList and preserved as the IN message body.
public Response doAction(DataHandler abcd); The DataHandler is unwrapped from the MessageContentsList and preserved as the IN message body.

More examples of the Simple Binding Style

Given a JAX-RS resource class with this method:
    @POST @Path("/customers/{type}")
    public Response newCustomer(Customer customer, @PathParam("type") String type, @QueryParam("active") @DefaultValue("true") boolean active) {
        return null;
    }
Serviced by the following route:
    from("cxfrs:bean:rsServer?bindingStyle=SimpleConsumer")
        .recipientList(simple("direct:${header.operationName}"));

    from("direct:newCustomer")
        .log("Request: type=${header.type}, active=${header.active}, customerData=${body}");
The following HTTP request with XML payload (given that the Customer DTO is JAXB-annotated):
POST /customers/gold?active=true

Payload:
<Customer>
  <fullName>Raul Kripalani</fullName>
  <country>Spain</country>
  <project>Apache Camel</project>
</Customer>
Will print the message:
Request: type=gold, active=true, customerData=<Customer.toString() representation>
For more examples on how to process requests and write responses can be found here.

Consuming a REST Request - Default Binding Style

The CXF JAX-RS front end implements the JAX-RS (JSR-311) API, so we can export the resources classes as a REST service. And we leverage the CXF Invoker API to turn a REST request into a normal Java object method invocation. Unlike the camel-restlet component, you don't need to specify the URI template within your endpoint, CXF takes care of the REST request URI to resource class method mapping according to the JSR-311 specification. All you need to do in Apache Camel is delegate this method request to a right processor or endpoint.
Here is an example of a CXFRS route:
private static final String CXF_RS_ENDPOINT_URI = "cxfrs://http://localhost:" + CXT + "/rest?resourceClasses=org.apache.camel.component.cxf.jaxrs.testbean.CustomerServiceResource";
 
 protected RouteBuilder createRouteBuilder() throws Exception {
    return new RouteBuilder() {
        public void configure() {
            errorHandler(new NoErrorHandlerBuilder());
            from(CXF_RS_ENDPOINT_URI).process(new Processor() {

                public void process(Exchange exchange) throws Exception {
                    Message inMessage = exchange.getIn();                        
                    // Get the operation name from in message
                    String operationName = inMessage.getHeader(CxfConstants.OPERATION_NAME, String.class);
                    if ("getCustomer".equals(operationName)) {
                        String httpMethod = inMessage.getHeader(Exchange.HTTP_METHOD, String.class);
                        assertEquals("Get a wrong http method", "GET", httpMethod);
                        String path = inMessage.getHeader(Exchange.HTTP_PATH, String.class);
                        // The parameter of the invocation is stored in the body of in message
                        String id = inMessage.getBody(String.class);
                        if ("/customerservice/customers/126".equals(path)) {                            
                            Customer customer = new Customer();
                            customer.setId(Long.parseLong(id));
                            customer.setName("Willem");
                            // We just put the response Object into the out message body
                            exchange.getOut().setBody(customer);
                        } else {
                            if ("/customerservice/customers/400".equals(path)) {
                                // We return the remote client IP address this time
                                org.apache.cxf.message.Message cxfMessage = inMessage.getHeader(CxfConstants.CAMEL_CXF_MESSAGE, org.apache.cxf.message.Message.class);
                                ServletRequest request = (ServletRequest) cxfMessage.get("HTTP.REQUEST");
                                String remoteAddress = request.getRemoteAddr();
                                Response r = Response.status(200).entity("The remoteAddress is " + remoteAddress).build();
                                exchange.getOut().setBody(r);
                                return;
                            }
                            if ("/customerservice/customers/123".equals(path)) {
                                 // send a customer response back
                                 Response r = Response.status(200).entity("customer response back!").build();
                                 exchange.getOut().setBody(r);
                                 return;
                            }
                            if ("/customerservice/customers/456".equals(path)) {
                                Response r = Response.status(404).entity("Can't found the customer with uri " + path).build();
                                throw new WebApplicationException(r);
                            } else {
                                throw new RuntimeCamelException("Can't found the customer with uri " + path);
                            }
                        }
                    }
                    if ("updateCustomer".equals(operationName)) {
                        assertEquals("Get a wrong customer message header", "header1;header2", inMessage.getHeader("test"));
                        String httpMethod = inMessage.getHeader(Exchange.HTTP_METHOD, String.class);
                        assertEquals("Get a wrong http method", "PUT", httpMethod);
                        Customer customer = inMessage.getBody(Customer.class);
                        assertNotNull("The customer should not be null.", customer);
                        // Now you can do what you want on the customer object
                        assertEquals("Get a wrong customer name.", "Mary", customer.getName());
                        // set the response back
                        exchange.getOut().setBody(Response.ok().build());
                }
                }
                
            });
        }
    };
}
The corresponding resource class used to configure the endpoint is defined as an interface:
@Path("/customerservice/")
public interface CustomerServiceResource {
 
    @GET
    @Path("/customers/{id}/")
    Customer getCustomer(@PathParam("id") String id);
 
    @PUT
    @Path("/customers/")
    Response updateCustomer(Customer customer);
 }
Important
By default, JAX-RS resource classes are used to configure the JAX-RS properties only. The methods will not be executed during the routing of messages to the endpoint, the route itself is responsible for all processing instead.
Note
Note that starting from Camel 2.15, it is also sufficient to provide an interface only, as opposed to a no-op service implementation class for the default mode. Starting from Camel 2.15, if the performInvocation option is enabled, the service implementation will be invoked first, the response will be set on the Camel exchange and the route execution will continue as usual. This can be useful for integrating the existing JAX-RS implementations into Camel routes and for post-processing JAX-RS Responses in custom processors.

How to invoke the REST service through camel-cxfrs producer ?

The CXF JAXRS front end implements a proxy-based client API, with this API you can invoke the remote REST service through a proxy. The camel-cxfrs producer is based on this proxy API. You just need to specify the operation name in the message header and prepare the parameter in the message body, the camel-cxfrs producer will generate the right REST request for you.
Here is an example:
Exchange exchange = template.send("direct://proxy", new Processor() {

    public void process(Exchange exchange) throws Exception {
        exchange.setPattern(ExchangePattern.InOut);
        Message inMessage = exchange.getIn();
        setupDestinationURL(inMessage);
        // set the operation name 
        inMessage.setHeader(CxfConstants.OPERATION_NAME, "getCustomer");
        // using the proxy client API
        inMessage.setHeader(CxfConstants.CAMEL_CXF_RS_USING_HTTP_API, Boolean.FALSE);
        // set a customer header
        inMessage.setHeader("key", "value");
        // set the parameters , if you just have one parameter 
        // camel will put this object into an Object[] itself
        inMessage.setBody("123");
    }
    
});
     
// get the response message 
Customer response = (Customer) exchange.getOut().getBody();

assertNotNull("The response should not be null ", response);
assertEquals("Get a wrong customer id ", String.valueOf(response.getId()), "123");
assertEquals("Get a wrong customer name", response.getName(), "John");
assertEquals("Get a wrong response code", 200, exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE));
assertEquals("Get a wrong header value", "value", exchange.getOut().getHeader("key"));
CXF JAXRS front end also provides a http centric client API, You can also invoke this API from camel-cxfrs producer. You need to specify the HTTP_PATH and Http method and let the the producer know to use the HTTP centric client by using the URI option httpClientAPI or set the message header with CxfConstants.CAMEL_CXF_RS_USING_HTTP_API. You can turn the response object to the type class that you specify with CxfConstants.CAMEL_CXF_RS_RESPONSE_CLASS.
Exchange exchange = template.send("direct://http", new Processor() {

    public void process(Exchange exchange) throws Exception {
        exchange.setPattern(ExchangePattern.InOut);
        Message inMessage = exchange.getIn();
        setupDestinationURL(inMessage);
        // using the http central client API
        inMessage.setHeader(CxfConstants.CAMEL_CXF_RS_USING_HTTP_API, Boolean.TRUE);
        // set the Http method
        inMessage.setHeader(Exchange.HTTP_METHOD, "GET");
        // set the relative path
        inMessage.setHeader(Exchange.HTTP_PATH, "/customerservice/customers/123");                
        // Specify the response class , cxfrs will use InputStream as the response object type 
        inMessage.setHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_CLASS, Customer.class);
        // set a customer header
        inMessage.setHeader("key", "value");
        // since we use the Get method, so we don't need to set the message body
        inMessage.setBody(null);                
    }
    
});
     
// get the response message 
Customer response = (Customer) exchange.getOut().getBody();

assertNotNull("The response should not be null ", response);
assertEquals("Get a wrong customer id ", String.valueOf(response.getId()), "123");
assertEquals("Get a wrong customer name", response.getName(), "John");
assertEquals("Get a wrong response code", 200, exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE));
assertEquals("Get a wrong header value", "value", exchange.getOut().getHeader("key"));
From Apache Camel 2.1, we also support to specify the query parameters from CXFRS URI for the CXFRS HTTP centric client.
Exchange exchange = template.send("cxfrs://http://localhost:" + getPort2() + "/" + getClass().getSimpleName() + "/testQuery?httpClientAPI=true&q1=12&q2=13"
To support the Dynamical routing, you can override the URI's query parameters by using the CxfConstants.CAMEL_CXF_RS_QUERY_MAP header to set the parameter map for it.
Map<String, String> queryMap = new LinkedHashMap<String, String>();                    
queryMap.put("q1", "new");
queryMap.put("q2", "world");                    
inMessage.setHeader(CxfConstants.CAMEL_CXF_RS_QUERY_MAP, queryMap);