Developing SOAP Web service using Apache CXF and Apache Camel

Overview

In this post, we will walk through the steps to develop a SOAP service using Java and we will also leverage Spring , Apache Camel & Apache CXF.

Pre-requisties

  1. JDK 1.8 for code development
  2. Maven for dependency management
  3. JBoss Fuse 6.3 for deployment

Code Development

Maven Project with required dependencies

Create a maven project using IDE and add following dependencies in the pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.customer.service</groupId>
<artifactId>customer-service-impl</artifactId>
<version>1.0.0</version>
<packaging>bundle</packaging>
<name>Customer Service</name>

<properties>
<camel.version>2.17.0</camel.version>
</properties>
<dependencies>
<!-- Camel Dependency -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-sql</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- For compliation of java code , you can specify the java version also -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<!-- For creating packing as bundle i.e. jar to bundle -->
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<version>2.4.0</version>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.7.8</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/wsdl/customer-service.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

About the components used in pom.xml

** Project structure **
project-folder-structure

WSDL & Schema

Designing Schema

You can refer this post for detail discussion on designing schema

Below is the schema which we will use in this example
src/main/resources/wsdl/customer-service.wsdl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://service.customer.com/types/v1"
xmlns:tns="http://service.customer.com/types/v1" elementFormDefault="qualified">
<xs:element name="createCustomerRequest" type="tns:CreateCustomerRequest" />
<xs:element name="createCustomerResponse" type="tns:CreateCustomerResponse" />
<xs:element name="createCustomerFault" type="tns:CreateCustomerFault" />
<xs:complexType name="CreateCustomerRequest">
<xs:sequence>
<xs:element name="customerName">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="50" />
<xs:whiteSpace value="collapse" />
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="customerAge">
<xs:simpleType>
<xs:restriction base="xs:integer" />
</xs:simpleType>
</xs:element>
<xs:element name="customerCity">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="50" />
<xs:whiteSpace value="collapse" />
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="customerPhoneNumber">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="10" />
<xs:whiteSpace value="collapse" />
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CreateCustomerResponse">
<xs:sequence>
<xs:element name="customerID" type="xs:integer" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="CreateCustomerFault">
<xs:sequence>
<xs:element name="errorMessage" type="xs:normalizedString" />
<xs:element name="errorCode" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:schema>

Designing WSDL

You can refer this post for detail discussion on designing wsdl.

Below is the WSDL which we will use in this example
src/main/resources/wsdl/customer-service.xsd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:cs="http://service.customer.com/types/v1"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://service.customer.com/v1"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="customer-service" targetNamespace="http://service.customer.com/v1">
<wsdl:types>
<xsd:schema>
<xsd:import namespace="http://service.customer.com/types/v1" schemaLocation="schemas/customer-service.xsd" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="createCustomerRequest">
<wsdl:part name="createCustomerRequest" element="cs:createCustomerRequest" />
</wsdl:message>
<wsdl:message name="createCustomerResponse">
<wsdl:part name="createCustomerResponse" element="cs:createCustomerResponse" />
</wsdl:message>
<wsdl:message name="createCustomerFault">
<wsdl:part name="createCustomerFault" element="cs:createCustomerFault" />
</wsdl:message>
<wsdl:portType name="CustomerServicePort">
<wsdl:operation name="createCustomer">
<wsdl:input message="tns:createCustomerRequest" name="createCustomerRequest" />
<wsdl:output message="tns:createCustomerResponse" name="createCustomerResponse" />
<wsdl:fault message="tns:createCustomerFault" name="createCustomerFault" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CustomerServiceBinding" type="tns:CustomerServicePort">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="createCustomer">
<soap:operation soapAction="createCustomer" style="document" />
<wsdl:input name="createCustomerRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="createCustomerResponse">
<soap:body use="literal" />
</wsdl:output>
<wsdl:fault name="createCustomerFault">
<soap:fault name="createCustomerFault" use="literal" />
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CustomerService">
<wsdl:port name="CustomerServicePort" binding="tns:CustomerServiceBinding">
<soap:address location="http://0.0.0.0:8181/cxf/customerservice/v1" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

XML Based Spring configuration

We’ll use the xml based spring configuration for this example to expose the service.

Define the CXF Endpoint

Now, we’ll define the CXF Endpoint to expose the service

  • address specify the path where you want to expose your service
  • serviceName & endpointName should be same as specified in wsdl,
  • wsdlURL relative to resources folder or it can a http url also.
1
2
3
<cxf:cxfEndpoint name="customerServiceCXF" address="/customerservice" 
endpointName="cs:CustomerServicePort" serviceName="cs:CustomerService"
xmlns:cs="http://service.customer.com/v1" wsdlURL="wsdl/customer-service.wsdl"/>
Define the CamelContext

All the camel routes will be written in the camel context, so we will create a camelContext in the same file.

1
2
3
<camel:camelContext>
<-- Camel routes here -->
</camel:camelContext>
Define the dataFormats

We will define the data format to convert the soap xml payload into java object and vice versa. Context path is the root folder where you have all the auto-generated classes (WSDL & schema objects created using codegen-plugin)

1
2
3
<camel:dataFormats>
<camel:jaxb id="customerJaxb" contextPath="com.customer.service.types.v1"/>
</camel:dataFormats>
Define the camel route

We’ll define the camel route based on the logic, in this example route is pretty small & simple, but trust me at enterprise level it can be bit complex and longer.

1
2
3
4
5
6
<camel:route id="customer-service-main-route">
<camel:from uri="cxf:bean:customerServiceCXF?dataFormat=PAYLOAD"/>
<camel:unmarshal ref="customerJaxb"/>
<camel:process ref="customerResponseProcessor"></camel:process>
<camel:marshal ref="customerJaxb"/>
</camel:route>
  • cxf component & bean id is used to lookup the cxfEndpoint.
  • camel:from will allow to consume the request(s) incoming the cxf endpoint.
  • camel:unmarshal is used for converting soap payload to java objects
  • camel:process is used for the logic to be performed in the service
  • camel:marshal is used for converting java object to soap xml.

This is how your routes file will look like
src/main/resources/META-INF/spring/bundle-context-routes.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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">
<cxf:cxfEndpoint name="customerServiceCXF" address="/customerservice" endpointName="cs:CustomerServicePort"
xmlns:cs="http://service.customer.com/v1" serviceName="cs:CustomerService" wsdlURL="wsdl/customer-service.wsdl" />
<camel:camelContext>
<camel:dataFormats>
<camel:jaxb id="customerJaxb" contextPath="com.customer.service.types.v1"/>
</camel:dataFormats>
<camel:route id="customer-service-main-route">
<camel:from uri="cxf:bean:customerServiceCXF?dataFormat=PAYLOAD"/>
<camel:unmarshal ref="customerJaxb"/>
<camel:process ref="customerResponseProcessor"></camel:process>
</camel:route>
</camel:camelContext>
<bean id="customerResponseProcessor" class="com.customer.service.processor.CustomerResponseProcessor"/>
</beans>

Response Processor

For this example, we will write a processor which stores the employee data in map and return customer ID.

Create CustomerResponseProcessor.java in src/main/resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.customer.service.processor;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.customer.service.types.v1.CreateCustomerRequest;
import com.customer.service.types.v1.CreateCustomerResponse;

public class CustomerRequestProcessor implements Processor {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomerRequestProcessor.class);
private static long counter = 1;
private static Map < Long, CreateCustomerRequest > customers = new HashMap();

@Override
public void process(Exchange exchange) throws Exception {
CreateCustomerRequest request = exchange.getIn().getBody(CreateCustomerRequest.class);
customers.put(counter, request);
synchronized(this) {
counter++;
}

CreateCustomerResponse response = new CreateCustomerResponse();
response.setCustomerID(BigInteger.valueOf(counter));

LOGGER.debug("Employee stored in map ID: " + counter);

exchange.getOut().setBody(response);
}

}

Code Build

Now we’ll compile the code & package it as .jar using maven.

1
$ mvn clean install

Deploy on JBoss Fuse

Once we have the jar file ready with us, we’ll deploy it JBoss Fuse 6.3. You can refer this post to learn more about the deployments on JBoss Fuse.

1
2
$ cd fuse/bin
$ ./fuse

jboss-fuse-welcome
Run following command for the deployment

JBossFuse:admin@root> install -s mvn:com.customer.service/customer-service-impl/1.0.0

Now you can check the service url by visiting below url
http://localhost:8181/cxf