CXF includes an "HTTP binding" which makes it easy to build REST style services. The HTTP binding allows you to take any operation and map it to arbitrary URIs and HTTP verbs (i.e. GET, PUT, POST, DELETE).
| This binding has been deprecated and is likely to be removed from CXF in one of its future releases. |
Convention based services
If you have a simple CRUD based Java class, CXF can try to build up a set of resources automatically for you with no annotations or configuration. This is best explained through an example. Lets take a look at a typical CRUD class:
import javax.jws.WebService;
@WebService
public interface PeopleService {
Collection<Person> getPeople();
Person getPerson(long id);
void addPerson(Person person);
void updatePerson(long id, Person person);
void deletePerson(long id);
}
| WebService Annotation
The @WebService annotation is required. |
Using CXF's convention based mapping, a set of resources will be created and mapped to these operations.
1: Collection<Person> getPeople() is mapped to an HTTP GET on /people
That's straightforward enough. We see "get", we map it to a GET operation. Then people is extracted from the operation name and turned into a simple URI. Accessing http://server/people would result in a document like so:
<getPeople>
<Person>...</Person>
<Person>...</Person>
</getPeople>
2: Person getPerson(id) is mapped to an HTTP GET on /people/{id}.
Here we're again extracting a resource name from the operation, but you'll notice we're pluralizing it. Underneath the covers CXF is using a class which can take most English words and pluralize them. There are also hooks where you can add your own signular to plural mappings or even your own language if you desire.
All the parameters from the operation get appended to the resource name. If there were two parameters, "bit" and "bob" it would become /people/{bit}/{bob}. CXF will only map bit and bob though if they're XML schema primitives (int, string, dateTime, etc).
3: void addPerson(Person person) is mapped to an HTTP POST on /people.
We're recognizing the "add" here as a "POST" operation. "create" is also acceptable. And again some pluralizer magic to map the add to /people.
4: void updatePerson(long id, Person person) is mapped to an HTTP PUT on /people/{id}.
The same rules here as #2 except we're mapping "update" operations to PUTs.
5: void deletePerson(long id) is mapped to an HTTP DELETE on /people/{id}
The same rules here as #2 and #4 except we're looking for "delete" and "remove" in the operation and mapping it to an HTTP DELETE.
Configuring the service
You can create a service which uses the HTTP binding by using JaxWsFactoryBean:
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setServiceClass(PeopleService.class);
sf.getServiceFactory().setWrapped(true);
sf.setBindingId(HttpBindingFactory.HTTP_BINDING_ID);
sf.setAddress("http:);
PeopleService peopleService = new PeopleServiceImpl();
sf.getServiceFactory().setInvoker(new BeanInvoker(peopleService));
Server svr = sf.create();
A couple things to note:
- The JaxWsServerFactory bean creates a Server inside CXF which starts listening for requests on the URL specified.
- The bindingId specifies how your service operations get mapped to resources: in this case the http binding is used.
- We're telling the ServiceFactory to work in "wrapped" mode. This just means that our xml documents will be wrapped with the operation name, allowing us to have multiple input parameters. More on this further down.
Java REST Annotations
The Java REST Annotations are annotations which provide information to CXF on how to map operations to arbitrary URI/HTTP verb combinations.
Lets say I want to build an HTTP service that shares and manipulates customer data. The first thing I might want to do is create a URI that returns a document of all the customers in my database. With the JRA annotations this would be done like so:
@Get
@HttpResource(location="/customers")
Collection<Customer> getCustomers();
The @Get annotation maps the operation to the GET verb and the @HttpResource maps it to the URI "/customers". The List gets sent off the databinding implementation you're using and will be serialized as a document. So far very simple!
Now lets say I want to pull down a specific customer, from /customers/ID:
@Get
@HttpResource(location="/customers/{id}")
Customer getCustomers(GetCustomer getCustomer);
The major new concept in this example is URI templates. The GetCustomer object has a single property named "id":
public class GetCustomer {
String getId() { .. }
void setId(String id) { .. }
The URI parameters get mapped to the XML document according to its schema and the WSDL 2 rules. So if you access the URL /customers/123 CXF will actually synthesize an incoming XML document like this:
<getCustomer><id>123</id></getCustomer>
The databinding layer will then convert this into the GetCustomer object. Lets move on to a more complex example - a PUT operation which updates the customer:
@Put
@HttpResource(location="/customers/{id}")
Customer updateCustomer(Customer customer);
Instead of synthesizing a document this time, we're actually merging the URI parameters into the incoming document. This means {id} will get mapped to the /customer/id of the incoming customer document. Since the document will most likely already have the customer ID in it, the {id} is primarily there to create nice looking URI.
For a final example, lets look at adding a customer:
@Post
@HttpResource(location="/customers")
void addCustomer(Customer customer);
This will allow users to do a POST to the /customers URI with their document and add it to the collection of customers contained there.
Example with multiple arguments
To use multiple arguments, the @WebParam annotation has to be used to map the parameters of the url to the service parameters.
For example:
@Get
@HttpResource(location="/customers/{first}/{last}")
void findCustomer(@WebParam(name="first") String firstName, @WebParam(name="last") String lastName);
Configuring the Service
Configuration for JRA style services is exactly the same as the convention based services. However, in this example, the service is not in "wrapped" mode. So the configuration is slightly different as we don't need to explicitly set the wrapped setting:
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setServiceClass(CustomerService.class);
sf.setBindingFactory(new HttpBindingInfoFactoryBean());
sf.setAddress("http:);
CustomerService customerService = new CustomerServiceImpl();
sf.getServiceFactory().setInvoker(new BeanInvoker(customerService));
Server svr = sf.create();
Configuring the service in container with Spring configuration file.
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
beans.xml
<?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:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-http-binding.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<jaxws:endpoint id="userService"
implementor="org.apache.cxf.CustomerServiceImpl"
address="/customerService"
bindingUri="http://apache.org/cxf/binding/http">
<jaxws:serviceFactory>
<bean class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean">
<property name="wrapped" value="true" />
</bean>
</jaxws:serviceFactory>
</jaxws:endpoint>
</beans>
| Useful Information
The JaxWsServiceFactoryBean is not resusable across various services. |
Wrapped vs. Unwrapped Mode
In REST style services we can only send and receive one XML element. Wrapping is the process of wrapping the XML requests/responses with the operation names to allow multiple parameters to your operation. For instance, say we had an operation "Customer findCustomer(String name, String company)". It would not be valid to create an XML POST request like this:
<name>Dan</name>
<company>Acme Inc</company>
That has two root XML elements, which isn't allowed. Instead we would have to "wrap" the POST with the operation name:
<findCustomers>
<name>Dan</name>
<company>Acme Inc</company>
</findCustomers>
You may be wondering why don't we always turn on wrapping? Well wrapping creates uglier XML. Take this operation for instance: Collection<Customer> getCustomers(). The resulting XML would be:
<getCustomersResponse>
<Customers>
<Customer>..</Customer>
<Customer>..</Customer>
</Customers>
</getCustomersResponse>
The getCustomersResponse element is not needed and makes the response uglier. CXF allows you to chose which style you desire. In Wrapped mode you can write your service interface however you desire.
In unwrapped mode you must only receive a single input and respond with a single output. If you wish to take multiple parameters, you must combine them in an object. With the findCustomer example above, you would want to create a FindCustomer type which name and company properties.