Overview of CXF
Transformation Feature
The CXF Transformation feature allows you to declaratively
define a transformation to change namespaces or append/change/drop elements.
The transformation is very much tied to the CXFEndpoint.
Intended
Use-cases
The use-cases for which the CXF transformation feature works
well are the following use-cases.
- Transforming Incoming
Requests from clients - A route exposes an interface to clients via a
WSDL. Some clients may not be
capable of sending messages (or receive response) that adhere to the
interface defined by the route’s WSDL. The CXF transform feature will
allow you to define transformations on the CXFEndpoint so that incoming
non-conforming messages from clients can be transformed and “fixed” to
adhere to the expected interface defined by the WSDL. This allows clients
to send non-conforming messages to the route (e.g. incorrect namespace or
element names) – messages that would otherwise cause an error when sent to
the route - and you can define the appropriate transformation on the
CXFEndpoint to “fix” the incoming requests to conform to WSDL, and
likewise modify outgoing (well-formed) response to something client
expects.
- Transforming Outgoing Requests to backend - A
route sends requests to backend service that are well-formed according to a
specific WSDL, but the backend service expects some tweak to what the route is
sending. This could apply for example, in the case where the route’s
CXFEndpoint (client) is ahead of the backend service on changes to element names
or namespaces that adhere to an enterprise standard. At some point the backend
will be updated to accept these changes. However until the backend service is
conformant, the transformation applied to the CXFEndpoint (client) in the route
will tweak the outgoing messages so that they can be understood by the backend
service, and likewise the responses from the backend service can be transformed
as needed into the well-formed format according to the enterprise WSDL.
Transforming
Incoming Requests from clients
Let's say we want our route to expose the interface defined
by a new "enterprise" WSDL, and we want to go ahead and deploy that.
Even if there are clients that are not ready to adhere to the new WSDL, clients
can continue to send requests that adhere to the "old" WSDL (e.g.
operation names, different namespace, different element names, etc.) Existing clients can continue to send
requests containing non-conforming messages.
In our new route, we just need to include a mapping in the
spring file that defines the transformation on incoming messages. So as long as
we add the transformation rules into our route, it is able continue to service
clients that don't necessarily adhere to the new WSDL, and (if needed) the
route will modify the messages as defined in the transformation.
Below shows the Spring configuration a route
that exposes the interface defined in ‘deqy.wsdl’. Imagine this is our “new” interface. If there
are clients that must continue to call our route that can’t change to the new
WSDL, then the route can be configured (via the Transformation mapping) to
accommodate those existing clients.
The above code snippet is a ‘camel-context.xml’ (Spring
configuration) for a Camel route that listens on a CXF endpoint. The CXF
endpoint points to a WSDL that defines the route’s interface. The route
includes a CXF transformation map that declaratively defines mappings that
should be done to non-conforming messages as they enter the route. Note the
mappings are defined in such a way that they won’t affect messages that do
adhere to the WSDL.
The above Spring defines a route starting at (line 43) that
begins with a CXF endpoint (listener). The CXF endpoint is defined at lines
32-38 with the transform feature being configured starting at line 35. The individual transformations are defined at
lines (20-29.)
As
you can see it is pretty straightforward to configure a route to accommodate
for payload transformation to be applied to incoming messages. Likewise it is
just as easy to apply a transformation for the corresponding response back to
client that will convert the compliant response from the route into a
(potentially non-conformant) response that the client expects.
Transforming
Outgoing Requests to backend
For a scenario where a route sends a request to a backend
server, you may have written your route CXFEndpoint (client) that adheres to a
particular WSDL but the backend service for some reason or another needs the
message to be tweaked slightly. To apply
a transformation to a request message before it is written to the wire and a
response before it gets dispatched to the route you can use an outgoing
transform.
The
following code shows the implementation of an outgoing transform feature.
Lines 29-42 define the transform mapping for ‘outTranformElements’
– the mapping on requests as they are sent from route to the backend service,
followed by ‘inTransformElements’ – the mapping on response before the message
re-enters the route.
See lines 44-51 for the CXFEndpoint ‘deqyService’, where the
transform feature is applied (lines 48-50).
The route starts at line 54. The route reads a message from
a file in ‘inbox’ directory, and sends it to the backend service. The route
receives the response, and writes response to a file in ‘outbox’ directory.
Consider
a test where the message content of the file dropped into ‘inbox’ conforms to
the WSDL defined on the outgoing
CXFEndpoint – i.e. ‘deqy.wsdl’.
Imagine the backend service requires a tweak to a well-structured
request. For example the namespace the backend service expects in incoming
request and writes to response is different from what is defined in the WSDL.
We can apply any such tweak required by backend service using a transform
defined on the endpoint. In this
scenario the well-formed message we drop into inbox will get transformed just
before it is sent to the backend service based on the transform defined in
‘outTranformElements’. Likewise, the response from backend service gets
transformed based on the ‘inTransformElements’ before the response message
re-enters the route.
A
Workable Solution using CXF transform Feature
The following diagram shows how a message flows through a
route that consists of incoming and outgoing CXF Endpoints. Both incoming and
outgoing CXF endpoints have transforms defined.
The location in the flow where the transforms are executed is shown in
the diagram.
Consider
a client that is written based on the interface defined by Service ‘A’
WSDL. The client sends a request to the
route using the format based on Service ‘A’ WSDL. The incoming ‘inTransform’
(see position 1 in diagram) will convert messages from the (non-conforming)
format sent by clients, into a format adhering to the Service ‘B’ WSDL. Without
this transform, the incorrectly formatted message sent from client would cause
an operation/binding mis-match when it hit the route’s CXFEndpoint that begins
the route, and this would cause the request to fail on entry to the route.
The message continues through the route, through Processor A
and B and then reaches the consumer CXFEndpoint. This CXFEndpoint will call the
backend Service ‘B’. The format of the message is correct (adheres to Service
‘B’ WSDL) and Service ‘B’ will respond with correctly formatted message. In
this solution, the ‘outTransform’ and ‘inTransform’ at position 2 and 3 are not
required.
The
response from Service ‘B’ flows back into the route, and the response goes
through Processor ‘C’ where a camel-based transformation (e.g. xquery, XSLT,
java code) can be used to transform/enrich the response in any way required.
This transformed message can now be returned back to client. The ‘outTransform’
(position 4) is not necessary in this solution.
Using
CXF in Generic Provider Mode
One aspect of the CXF transformation feature that is
problematic is the requirement that you are restricted to performing transformations
that convert message into something that conforms with the WSDL the endpoint is
tied to. If the message is transformed in a way that makes it non-conformant,
an error is thrown when it reaches the endpoint.
In order to make the use of CXF transform easier within
Camel, there is a way to disable the requirement that the incoming message
match the operation/binding of the associated CXFEndpoint. That way, if this message-level
validation is disabled on the Endpoint you can freely apply tranformations to
any format at the endpoint and it will continue to pass through the endpoint
without failure.
You should be careful when over-riding this as the purpose
of a WSDL is to do the operation checking and some special handling based on
the determined operation and also to provide a way for the consumer to get the
typed interface information (i.e., retrieving the WSDL from the endpoint). By
disabling, you will do neither of these, so you could just simply set up your
endpoint using the generic provider mode (i.e., omitting the wsdlURL and
serviceClass properties of the endpoint).
In this case, the message will be transformed using CXF's
transform feature and directly transferred to your camel route where you can do
further modification necessary and set the operationName header to match the
operation at the outbound endpoint
In
this way, you can allow messages to be transformed using CXF transform feature
without the problem of adhering to a particular interface.
Summary
The transformation capabilities of the CXF transform mapping
language are limited. The transform language works well for minimal changes
such as element name changes or namespace changes. More involved e.g.
structural changes to complex types or multi-faceted elements are not easily
implemented using the mapping language.
A CXF transform feature is applied to a specific CXFEndpoint
in a route. Because the transform is tightly tied to the specific CXFEndpoint,
it cannot be used to apply a transformation at random spots on a message flow
through the route. This limits the usage of the CXF transform feature as a
general mapping tool for transformations within a camel route.
In
order to make the CXF transform feature a useful technique for performing
general transformations to messages as they flow through a route, you will
likely need to disable the opearation validation of messages at the CXFEndpoint
and use the generic provider mode. This will allow for further modifications in
the transform feature than would normally be possible if you used the default
strictly-typed message checks that typically are part of the message entry into
the route.