In this tutorial we will show how to proxy a request to a legacy JAX-WS Web service using Camel.
A common scenario is that you have some legacy code (say some Web services) which cannot be reused becuase the interfaces or some conventions (e.g. namespaces) have changed. Proxing request to legacy code can also be a need when you need to monitor or debug some functions which are available on a middleware.
In the Camel quickstarts https://github.com/apache/camel/tree/master/examples we can find an example named camel-example-cxf-proxy that implements this pattern. I will adapt this example to simulate a real world scenario, such as the following one:
Creating the Legacy JAX-WS Endpoint
The source code for the Legacy JAX-WS Endpoint is available here: https://github.com/fmarchioni/masterspringboot/tree/master/camel/cxf-proxy
This project contains the following JAX-WS Web service:
package org.apache.camel.example.cxf.proxy; import org.apache.camel.example.reportincident.InputReportIncident; import org.apache.camel.example.reportincident.OutputReportIncident; import org.apache.camel.example.reportincident.ReportIncidentEndpoint; @WebService( targetNamespace = "http://reportincident.example.camel.apache.org", name = "ReportIncidentEndpoint") public class ReportIncidentEndpointService implements ReportIncidentEndpoint { public OutputReportIncident reportIncident(InputReportIncident in) { System.out.println( "nnnInvoked real web service: id=" + in.getIncidentId() + " by " + in.getGivenName() + " " + in.getFamilyName() + "nnn"); OutputReportIncident out = new OutputReportIncident(); out.setCode("OK;" + in.getIncidentId()); return out; } }
The structure of the InputReportIncident is stated in the WSDL as follows:
<xs:complexType> <xs:sequence> <xs:element name="incidentId" type="xs:string"/> <xs:element name="incidentDate" type="xs:string"/> <xs:element name="givenName" type="xs:string"/> <xs:element name="familyName" type="xs:string"/> <xs:element name="summary" type="xs:string"/> <xs:element name="details" type="xs:string"/> <xs:element name="email" type="xs:string"/> <xs:element name="phone" type="xs:string"/> </xs:sequence> </xs:complexType>
Let’s deploy this Web Service on WildFly. Thanks to the WildFly plugin we can do it with just one command:
$ mvn clean install wildfly:deploy
The Web service will be available at the following Endpoint address:
http://localhost:8080/targetWS/ReportIncidentEndpointService
Creating the Camel CXF Proxy
The source code for this example is avalable at: https://github.com/fmarchioni/masterspringboot/tree/master/camel/cxf-proxy
Now let’s edit the camel-cxf-proxy example from Camel distribution to use as target Web Service the JAX-WS Web service running on WildFly at: http://localhost:8080/targetWS/ReportIncidentEndpoint
We then use a simple Java Bean to manipulate the content of the SOAP packet, so that the Web service is compatible with the legacy Web service.
Here is the camel-context.xml file that we will use:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camel="http://camel.apache.org/schema/spring" xmlns:cxf="http://camel.apache.org/schema/cxf" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd"> <!-- spring property placeholder, ignore resource not found as the file resource is for unit testing --> <context:property-placeholder location="classpath:incident.properties,file:target/custom.properties" ignore-resource-not-found="true"/> <!-- bean that enriches the SOAP request --> <bean id="enrichBean" class="org.apache.camel.example.cxf.proxy.EnrichBean"/> <!-- this is the CXF web service we use as the front end --> <cxf:cxfEndpoint id="reportIncident" address="http://localhost:${proxy.port}/camel-example-cxf-proxy/webservices/incident" endpointName="s:ReportIncidentEndpoint" serviceName="s:ReportIncidentEndpointService" wsdlURL="etc/report_incident.wsdl" xmlns:s="http://reportincident.example.camel.apache.org"/> <!-- this is the Camel route which proxies the real web service and forwards SOAP requests to it --> <camelContext xmlns="http://camel.apache.org/schema/spring"> <!-- property which contains port number --> <propertyPlaceholder id="properties" location="classpath:incident.properties,file:target/custom.properties"/> <endpoint id="callRealWebService" uri="http://localhost:8080/targetWS/ReportIncidentEndpointService"/> <route> <!-- CXF consumer using MESSAGE format --> <from uri="cxf:bean:reportIncident?dataFormat=RAW"/> <!-- log input received --> <to uri="log:input"/> <!-- enrich the input by ensure the incidentId parameter is set --> <to uri="bean:enrichBean"/> <!-- Need to remove the http headers which could confuse the http endpoint --> <removeHeaders pattern="CamelHttp*"/> <!-- send proxied request to real web service --> <to uri="ref:callRealWebService"/> <!-- log answer from real web service --> <to uri="log:output"/> </route> </camelContext> </beans>
We just need to code the EnrichBean. This class is used to enrich the proxied Web service to ensure the input is valid and add additional information:
package org.apache.camel.example.cxf.proxy; import org.w3c.dom.Document; import org.w3c.dom.Node; public class EnrichBean { public Document enrich(Document doc) { Node node = doc.getElementsByTagName("incidentId").item(0); String incident = node.getTextContent(); // here we enrich the document by changing the incident id to another value // you can of course do a lot more in your use-case node.setTextContent("456"); System.out.println("Incident was " + incident + ", changed to 456"); return doc; } }
Finally, we use the Unit Test class from the camel-cxf-proxy example to send a SOAP Request and assert that the return code is correct:
package org.apache.camel.example.reportincident; import java.io.File; import java.io.FileOutputStream; import org.apache.camel.spring.Main; import org.apache.camel.test.AvailablePortFinder; import org.apache.camel.util.FileUtil; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; public class ReportIncidentRoutesTest { // should be the same address as we have in our route private static String url; protected Main main; @BeforeClass public static void setupFreePort() throws Exception { // find a free port number, and write that in the custom.properties file // which we will use for the unit tests, to avoid port number in use problems int port = AvailablePortFinder.getNextAvailable(); String s = "proxy.port=" + port; int port2 = AvailablePortFinder.getNextAvailable(); String s2 = "real.port=" + port2; File custom = new File("target/custom.properties"); FileOutputStream fos = new FileOutputStream(custom); fos.write(s.getBytes()); fos.write("n".getBytes()); fos.write(s2.getBytes()); fos.close(); url = "http://localhost:" + port + "/camel-example-cxf-proxy/webservices/incident"; } @AfterClass public static void cleanup() { File custom = new File("target/custom.properties"); FileUtil.deleteFile(custom); } protected void startCamel() throws Exception { if (!"true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"))) { main = new Main(); main.setApplicationContextUri("META-INF/spring/camel-config.xml"); main.start(); } else { System.out.println("Skipping starting CamelContext as system property skipStartingCamelContext is set to be true."); } } protected void stopCamel() throws Exception { if (main != null) { main.stop(); } } protected static ReportIncidentEndpoint createCXFClient() { // we use CXF to create a client for us as its easier than JAXWS and works JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(ReportIncidentEndpoint.class); factory.setAddress(url); return (ReportIncidentEndpoint) factory.create(); } @Test public void testReportIncident() throws Exception { // start camel startCamel(); runTest(); // stop camel stopCamel(); } protected void runTest() throws Exception { // create input parameter InputReportIncident input = new InputReportIncident(); input.setIncidentId("123"); input.setIncidentDate("2008-08-18"); input.setGivenName("Claus"); input.setFamilyName("Ibsen"); input.setSummary("Bla"); input.setDetails("Bla bla"); input.setEmail("davsclaus@apache.org"); input.setPhone("0045 2962 7576"); // create the webservice client and send the request ReportIncidentEndpoint client = createCXFClient(); OutputReportIncident out = client.reportIncident(input); // assert we got a OK back Assert.assertEquals("OK;456", out.getCode()); } }
Run the test with:
$ mvn clean install
The test should complete successfully and on the WildFly console you should have the following log:
Invoked real web service: id=456 by Claus Ibsen
Do you want to see how to deploy the Project as OSGI bundle on JBoss Fuse ? Then check the next tutorial: Proxy Web Services request with JBoss Fuse