Pragmatic Web Services With Apache CXF
Join the DZone community and get the full member experience.
Join For FreeApache CXF [1] is an open-source Java-based framework whose goal is to help users develop standard-based, interoperable and robust web services.
The ultimate goal of writing web services is to ensure that a given web service implementation can be consumed by a variety of design- and run-time clients in an interoperable and effective manner. A lot has been written recently about how web services have to be designed. It appears that arguments are mostly over now with REST and SOAP communities proceeding with implementing web services they way they see fit most.
Apache CXF, by implementing various SOAP-based Web Services standards and a JAX-RS 1.0 specification for writing RESTful services in Java, provides a unique environment for users to develop advanced web services endpoints and clients using whichever style they like or need. It is of little doubt that the real world web services projects will very often involve a number of services written with REST or/and SOAP in mind and CXF is well aware of it – it is a progressive yet a pragmatic framework after all.
This article will not focus on contrasting REST and SOAP styles. So much has been written about it. Nor will we describe how to build and do a step by step web services development. Rather, we will describe how Apache CXF makes it easier for people to write both RESTful and SOAP XML-based service endpoints and consumers which can survive the changes to the underlying data for longer.
Backward and Forward Compatible Web Services
There is a number of well-known reasons why the WEB has succeeded. Undoubtedly, the ability of generic HTTP consumers to ignore unrecognised HTML tags and the fact that newly upgraded HTTP consumers aware of new HTML extensions are able to consume the older HTML documents has played one of the major roles.
The goal of writing backward and forward compatible web services applies equally to both REST and SOAP services, on both client and server sides. It is worth trying to put some effort in advance for this goal be achieved and it is great when the framework can help. Note that even though we assume XML is the underlying data format, some of the techniques described below can be applied when other data formats are involved.
So what does it mean to have a backward and forward compatible web service implementation?
- backward compatible with its older clients if it can continue consuming their requests, without them having to be recompiled
- forward compatible with its older clients if they can continue consuming this updated endpoint’s responses, without them having to be recompiled.
- forward compatible with the older endpoint if it can continue invoking on it
- backward compatible with the older endpoint if it can continue consuming its responses
Backward and forward compatibility is often taken for granted when well-behaved generic clients such as browsers consume the data. When dealing with compiled consumers, more discipline is required.
Often, the backward compatibility is easier to achieve for cases when the schema validation is enabled and it was a non-breaking update that was applied. Forward compatibility is essentially about ignoring unrecognised tags but can be trickier to achieve if the validation is enabled as it might require some advanced schema design. In other cases it actually can be unsafe to ignore unrecognised tags.
A breaking change is often indicated by a namespace change but in the real world it can be made to the document without its namespace being updated or indeed a non-breaking change can be indicated by the update to the namespace. There are many variations and as in the case with web services themselves, there will always be different scenarios to deal with.
In CXF, one can tackle many of the problems on the way to writing robust backward and forward compatible web service implementations. We will start from describing a sample RESTful application, show how both REST and SOAP can be combined in a single web service endpoint and then we will explore different options available in CXF for this endpoint to evolve and stay backward and forward compatible with its consumers.
Developing RESTful services
Apache CXF implements a JAX-RS 1.0 specification (JAX-RS). JAX-RS is a very good effort popularized by the JAX-RS Reference implementation. It describes how existing Java classes can be exposed as RESTful service endpoints with the help of annotations. It offers a number of extensions, like other JAX-RS implementations do. Among those extensions is a RESTful client API in proxy-based, http- and xml-centric flavours.
Lets write a simple BookStore application, keeping the goal of this article in mind. BookStore is a store which can let its clients find the books using some search criteria. Note that an assumption is made that users prefer writing a typed Java code which operates with domain-specific types. Many of the issues discussed below may be avoided by depending on types such as JAXP Source for example.
Service implementation
Here is an initial version of the BookStore interface and its implementation:
package org.bookstores;
import javax.ws.rs.*;
@Path(“/bookstore”)
public interface BookStore {
/**
* Find a Book by its id
**/
@Get @Path(“{id}”) @Produces(“application/xml”)
Book getBook(@PathParam(“id”) long id);
}
public class BookStoreImpl implements BookStore {
private Map<String, Book> books = new HashMap<String, Books>();
public Book getBook(long id) {
return books.get(id);
}
private void populateBooks() {}
}
Initially we have provided a BookStore implementation which will let people search the books by embedding a book identifier in a request URI path segment. For example, the following requests:
GET /bookstore/1
GET /bookstore/2
Will result in 1 and 2 mapped into an ‘id’ parameter of BookStoreImpl#getBook(long id)
Writing a RESTful client
So now that we have an application endpoint it is time to write a client code which will consume it. At the moment, CXF offers RESTful client API in 3 flavours : proxy-based, http-centric and xml-centric. Please consult the CXF JAX-RS documentation [2] for a complete description of the client API. Here is how you can consume a BookStore endpoint.
- Proxy-based approach :
import org.bookstores.*;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
Book b = store.getBook(1);
store.getBook(1) results in a “GET http://books/bookstore/1” request being sent to a BookStore endpoint. Proxies hide away the details of how a request URI should be formed and convert method parameters into corresponding URI component values or HTTP headers or body.
The proxies have pros and cons but for the purpose of this article it is suffice to say that a proxy-based approach tests really well how a corresponding endpoint implementation has prepared for unavoidable changes lying ahead.
Consider a new requirement coming in : find a book by name. First we need to update a BookStore interface which will require the old client code be recompiled as soon as it gets an updated interface on its classpath. We could have avoided an update to the interface if we chose to go without the interface in the first place and adding all the JAX-RS annotations to the BookStoreImpl.
It would not solve all the problems though and it would constrain the way BookStoreImpl will be able to act as a subresource. In other words, you would likely have to have a subresource method returning Object as opposed to BookStore interface whenever you decide that BookStoreImpl can also act as a JAX-RS subresource – it will be a valid but not typed Java code. So we just go and update the BookStore interface and hope never do it again :
@Path(“/bookstore”)
public interface BookStore {
@Get @Path(“{id}”) @Produces(“application/xml”)
Book getBook(@PathParam(“id”) long id);
@Get @Path(“name/{name}”) @Produces(“application/xml”)
Book getBookByName(@PathParam(“name”) String name);
@Get @Path(“{id}/{name}”) @Produces(“application/xml”)
Book getBookByNameAndId(@PathParam(“name”) String name, @PathParam(“id”));
}
Note that a new path segment “name” has to be added to a BookStore#getBookByName @Path value, otherwise both methods would match a request like “GET /bookstore/1” as per the JAX-RS method dispatching algorithm. This is not a perfect solution, as guess what, there will be a requirement coming in shortly to find the books by author.
It is also not clear what to do if we need to get a book by name and by id as we can invoke a single method at a time only. We added a composite getBookByNameAndById() method - it will mitigate but just postpone the problem – and it will be the case even if query parameters are used.
A more intrusive approach would be to have a void signature and use injected JAX-RS UriInfo context and use it to query the available path, matrix or query parameters – it will actually work but it may be just not the kind of code you’re after if you would like to use the domain specific types only.
So what about the client which wants to find a book by either a name, by id or both by name and id ? People are often imagining nice HTML forms but compiled clients are still out there too. Here is one option :
Map<String, String> argNameValuePairs = getArgumentsFromCmd();
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.getBook(1);
} else if (args.containsKey(“name”)) {
Book b = store.getBookByName(args.get(“name”));
} else if (args.containsKey(“name”) && args.containsKey(“id”)) {
Book b = store.getBookByNameAndId(args.get(“id”), args.get(“name”));
}
// more branches to follow
How can we avoid branching every time a new serach requirement comes in ? Read on please.
2. HTTP-centric clients
Here is a web client code given the requirement above:
import org.bookstores.*;
import org.apache.cxf.jaxrs.client.WebClient;
WebClient store = WebClient.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.path(args.get(“id”)).get(Book.class);
} else if (args.containsKey(“name”)) {
Book b = store.path(“/name/” + args.get(“name”) ).get(Book.class);
} else if (args.containsKey(“name”) && args.containsKey(“id”)) {
Book b = store.path(args.get(“name”)).path(args.get(“id”)).get(Book.class);
}
3. XML-centric clients
XML centric clients are WebClients relying on CXF XMLSource utility class. They are great when one needs to extract an expected XML representation out of the larger XML document so we will discuss them later on. Here is a simple example:
WebClient store = WebClient.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.path(args.get(“id”)).get(XMLSource.class).get(“/book”, Book.class);
}
// continue branching
Combining REST and SOAP services
Before proceeding to describing the proposed changes to out BookStore interface, lets focus a bit on how JAX-RS and JAX-WS annotations can be combined.
Many CXF users are continuing to write SOAP services. Many of such services are very advanced as CXF provides a lof of tools for SOAP developers to write sophisticated SOAP-based applications with its support for asynchronous and suspended invocations, its support for standards like WS-Security and WS-SecurityPolicy, its very flexible configuration options.
As we know, all SOAP over HTTP requests are done using POST. It is something that many end-to-end SOAP applications are not very concerned about – what matters is that such applications simply work as expected. However it is not straightforward enough for those SOAP endpoints which would like to have generic WEB clients out there be able to work with such endpoints to actually make it happen. Granted, tools like Flash or GWT let people consume SOAP services from browsers, but no linking can be done. Existing GET support for SOAP can work but it is not widely used.
A great number of WEB applications work by simply linking to each other. In other words, they use GET. GET is a major WEB verb is that all the information search and analysis is done by mostly using GET. Specific Web applications will also use POST/PUT/etc, but without GET WEB would not be the WEB really.
JAX-WS Provider and Dispatch mechanism does let developers to write functional RESTful applications and indeed one can imagine combining SOAP and REST applications with its help. For users preferring the Java interfaces dealing with expected user types JAX-RS 1.0 is the perfect match.
The major effect of JAX-RS 1.0 specification is that it allows an existing Java class be exposed as a web service by applying JAX-RS annotations to it. So if you already have say a JAX-WS annotated SOAP service then it’s straightforward to have a service which will get both REST and JAXWS invocations coming in:
import javax.ws.rs.*;
import javax.jws.*;
@Path(“/bookstore”)
@WebService
public interface BookStore {
@Get @Path(“{id}”) @Produces(“application/xml”)
@WebMethod
Book getBook(@WebParam(name=”id”) @PathParam(“id”) long id) throws BookUnavailableException;
}
Thus WEB-enabling a given SOAP node becomes a trivial task. Or SOAP-enabling a given RESTful endpoint – you never know what kind of requirement might pop up later on.
Now, the same problem we discussed earlier applies here : how do we write service interface which will anticipate the change.
Predicting the change
Before trying to figure out what to do for the existing code to stay alive in the face of unexpected data changes, it is always worth thinking a bit ahead and trying to anticipate the change. It is difficult, tricky and sometimes next to impossible to write a perfect code in this regard, but putting some thoughts from the start will likely save you from some headaches down the road.
Use single complex method parameters
Yes, the Java is rich and it lets you add many methods to a given class. But it is the Java code whose job is to face the web services world out there which is what you are working upon. Try to get a single composite input and return parameter only for a given method – if you prefer fine-grained parameters then you might want to delegate to another internal class instead.
Lets change the BookStore and BookStoreImpl like this:
@Path(“/bookstore”)
@WebService
public interface BookStore {
@Get @Path(“{id}/{name}/”) Produces(“application/xml”)
@WebMethod
Book getBook(@PathParam(“”) BookDescription descr);
@Get @Path(“search”) Produces(“application/xml”)
@WebMethod(exclude=”true”)
Book getBookByQuery(@QueryParam(“”)) BookDescription descr);
}
public class BookStoreImpl implements BookStore {
private Map<String, Book> localBooks = new HashMap<String, Books>();
public Book getBook(BookDescription desc) {
return doGetBook(desc);
}
public Book getBookByQuery(BookDescription desc) {
return doGetBook(desc);
}
public Book doGetBook(BookDescription desc) {
for (Book b : books.values()) {
if (-1 != desc.getId() && b.getId() != desc.getId()) {
continue;
}
if (null != desc.getId() && !b.getName().equals(desc.getName())) {
continue;
}
// reflectively check the values of other getters, if they not null then we can not
// satisfy the search constraints – most likely it a request from a new producer
return b;
}
return null;
}
}
public class BookDescription {
public long id;
public String name;
}
BookStore interface has two methods, one letting users to specify search parameters as path segment values, the other one as query parameters. Noth that both methods rely on a CXF JAX-RS extension which lets all the available name-value parameter values injected into a bean. For example, in case of PathParams, a PathParam “id” will have its value injected into an id field. For query and matrix parameters it works the same way really.
The method which deals with PathParams will not handle queries with an author name – still it will be less intrusive to get a @Path annotation updated only. As for the queries case this code will rarely have to change again – assuming the actual query request is delegated to a database procedure for example – but even if it had to, these changes would be very isolated with no side effects for clients : perhaps we’d only have to update doGetBook() to check for new bean properties. BeanDescription will likely be regenerated from an external schema whenever new search requirements occur.
Note that for a SOAP-based service the trick is to ensure that BookDesciption parameters are not unwrapped into a method signature, make sure the only parameter which is unwrapped is the BookDescription itself.
So what about the clients ?Here is a proxy based one:
BookDescription bookDescription = getBookDescriptionFromArguments();
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
BookStore jaxWSstore = createSoapProxy(“http://books/bookstore”);
searchBook(store);
searchBook(jaxWSstore);
private void searchStore(BookStore store, BookDescription desc) {
store.getBook(desc);
store.getBookByQuery();
}
Note that both REST and SOAP proxies can be reused in the same code and this code is unlikely to change often, whatever the new change requirements are. In case of RESTful proxies, BookDescription property values will be unwrapped into URI path values or query parameters, as needed.
Here is an HTTP-centric code:
BookDescription bookDescription = getBookDescriptionFromArguments();
WebClient store = WebClient.create(“http://books/bookstore”);
Book b1 = store.path(bookDescription).get(Book.class);
Book b1 = store.back().query(bookDescription).get(Book.class);
The
only explicit knowledge encoded here is .path() and .query() requests,
that is we need to know that it has to be a query or path parameters,
as opposed to the case with proxies. Other than that this code will
unlikely to change often. BookDescription property values will be
unwrapped as needed, to either path or queries. Also note store.back()
– you can effectively browse with a given WebClient using its back and
forward functions.
Toward backward and forward compatibility
With the above updates, we have backward and forward compatible clients and endpoints. The new requirements on how to search the book will likely affect the BookDescription only and the changes will be driven by updates to a BookDescription schema first.
New clients, those aware of new BookDescription properties, will continue invoking successfully on the old BookStore endpoints and thus will be forward compatible, while the newer BookStore endpoints will continue handling requests from the older ones.
Having
accumulated all the various search properties into a single bean we
made it possible to drive the updates from the outside, by updating the
schema and regenerating the bean definition and minimizing the need to
update the actual code.
As
if only things were that simple. As it happens, in reality, both
response Book and input BookDescription are actually namespace
qualified on the wire and schema validated on the input. Or you may
have got a new requirement to search books using an intermediary
service which interposes an original BookStore service and wraps the
resulting Book in a bigger document.
Apache
CXF is well equipped to help you to tackle many of the challenges on
the way to writing cost-effective clients and endpoints which know how
to deal with the change.
Typically, one would want to translate the incoming or out coming document to the format expected and in CXF one can do it at different levels, by post-processing a response object which will affect the serialization, by plugging in into an XML instance read and write process, transforming XML or extracting the required XML node.
Using RequestHandler, ResponseHandler and CustomInvokers
The core of the CXF JAX-RS implementation is implemented by two CXF interceptors[1], handling in and out messages. CXF JAX-RS also offers filters, RequestHandler and ResponeHandler, which can be assembled into chains and executed before a request body is de-serialized and a response object is serialized respectively.
Thus one somewhat low-level option is to register either RequestHandler and/or RequestHandler implementations and wrap an input or output stream:
public class InRequestHandler implements RequestHandler {
public Response handleRequest(Message m, ClassResourceInfo resource) {
m.putContent(InputStream.class, new CustomFilterInputStream(m.getContent(InputSream.class)));
}
}
Similarly, an output stream can be replaced in a ResponseHandler implementation.One can also update various request headers or indeed a request method if needed.
CXF Invokers is really yet another form of interceptors where one can validate the actual method paramer values and in fact replace either one of such parameters or a response object, with the latter option providing for the actual output customization. Replacing a response object can also be done in a ResponseFilter. Please check CXF JAX-RS documentation for more details.
Using CXF Interceptors
As
noted in the previos subsection, CXF implements JAX-RS with the help of
a couple of CXF interceptors. One can also register in or out custom
CXF interceptors which will further pre and post process a given HTTP
request or response, before or after JAX-RS runtime interceptors are
invoked. Using CXF interceptors to deal with data changes can be
particularly useful when combining SOAP and REST services as the same
interceptor instances can be registered and reused with both JAX-WS and
JAX-RS endpoints or clients.
Unexpected changes to non-XML formats can be dealt with in either CXF interceptors or JAX-RS filters described above.
Using Stax readers and writers.
It is been a long time since writing SAX handlers was considered to be the most effective XML processing option – STAX is here now and it is performing well and is arguably simpler for people to plug in into. In CXF JAX-RS we will add support for SAX handlers, SAX is still great and fast after all, but STAX is what is supported explicitly by both JAX-WS and JAX-RS runtime in CXF and using custom readers or writers is probably the best option for people writing combined SOAP and REST services and wishing to customize input or output XML messages.
Suppose the task is to translate a namespace. Here is how you can register custom readers and writers from CXF JAX-RS filters :
public class InRequestHandler implements RequestHandler {
public Response handleRequest(Message m, ClassResourceInfo resource) {
XMLStreamReader reader =StaxUtils.createXMLStreamReader(m.getContent(InputStream.class));
m.putContent(XMLStreamReader.class,
new CustomXMLStreamReader(inMessage, reader));
return null;
}
public Response handleResponse(Message outMessage, OperationResourceInfo ori, Response response) {
// did input XMLStreamReader translated a namespace ?
if (outMessage.getExchange().getInMessage().get(@namespace.translated@) == null) {
return;
}
XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(m.getContent(OutputStream.class)); m.setContent(XMLStreamWriter.class, new CustomXmlStreamWriter(writer));
return null;
}
public class CustomXmlStreamWriter extends DelegatingXMLStreamWriter {
private Message outMessage;
public CustomXmlStreamWriter(XMLStreamWriter writer) {
super(writer);
}
@Override
public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
super.writeStartElement("b", local, "");
if ("Book".equals(local)) {
super.writeNamespace("b", "http://oldbooks");
}
}
}
The CXF JAX-RS response filter checks if a namespace translation has occurred for a given message exchange and if yes then it installs a custom XMLStreamWriter which translates a namespace for older clients to continue operating. This is a technique which has been recently tried by one of CXF JAX-WS users, by using CXF interceptors to register custom STAX readers/writers instead.
Stax readers can be very useful when implementing a controlled forward compatibility policy described at [4], when it can be unsafe to ignore a given unknown tag. A custom reader will check a version attribute and will throw an exception which will be mapped to a proper HTTP response or a SOAP fault.
Using XSLT provider
XSLT is a very powerful technology for transforming XML documents. CXF JAX-RS offers a JAXB-based XSLT provider which can translate inbound and outbound XML instances.
In fact, depending on how far would you like to go with XSLT and if you use JAXB, this XSLT provider may be the only one provider ever needed for producing and consuming any types of well-formatted XML documents on both client and server sides, conforming to any XML-aware media types such as application/xml, application/xhtml+xml or application/atom+feed – you only need to have media type specific XSLT templates registered with the provider. Please see the CXF documentation on how to register this provider.
So you can handle incompatible changes at the pure XML level by leveraging the power of XSLT, at the input and output.
When producing, this provider can easily generate non-XML documents as well, for example it can be used toadapt a JAXB-produced XML to a natural JSON format expected by JSON clients.
Using XPath
XPath is another powerful XML technology. XPath is used by XSLT but you can use Xpath in CXF JAX-RS on its own. Explicitly working with XPath can be handy when you want to get a required XML fragment out of the larger parent document or when you simply would like to introspect various bits and pieces of the incoming XML or get to a single property without worrying a lot about its location in the XML document.
CXF JAX-RS introduces a light-weight utility class XMLSource which can be used either on the client or server sides, directly or as a provider, for example :
@Path(“/”)
public class Resource {
@POST
void postBody(XMLSource source) {
// get the very first book in the doc
Book book = source.getNode(‘/*/book[position()=1]’, Book.class);
}
}
WebClient client = WebClient.create(‘http://books’);
XMLSource source = client.get(XMLSource.class);
Book book = source.get(‘/*/book[position()=1]’, Book.class);
And finally, using XpathProvider, which is more suitable for out task of saving the client or server Java code from the recompilation:
XPathProvider provider = new XpathProvider();The provider can also be registered on the server side, both programmatically or from Spring.
provider.setExpression(‘/*/book[position()=1]’);
WebClient client = WebClient.create(‘http://books’, Collections.singletonList(provider));
Book book = client.get(Book.class);
With XPath and and XSLT providers in place and support for custom STAX handlers CXF can act as a micro router which applies transformation rules to incoming or outcoming messages.
Custom JAX-RS message body providers.
One can also imagine writing a custom MessageBodyReader or MessageBodyWriter which will do a custom (de)serialization from IntputStream/to OutputStream, adapting the data as expected by the ultimate receiver, by using additional properties. For example, one can imagine every client sending an HTTP header indicating a version. A message body provider finding a mismatch between the version it is aware of and the one sent by a current client can translate the data as needed – but it does require much more discipline and argubaly is more difficult to implementas one may need to register a number of class-specific providers.
Conclusion.
In this article we tried to focus on the issue which is very often ignored or viewed as not important or simply difficult to deal with. Hopefully, you can now see that achieving backward and forward compatibility is doable and worthy spending time upon and working with CXF does make tackling this challenge like a fun.
Links.
- Apache CXF: http://cxf.apache.org/
- CXF JAX-RS documentation: http://cwiki.apache.org/CXF20DOC/jax-rs.html
- Versioning Techniques and Strategies chapter in a Web Service Contract Design and Versioning book which brings David Orchards posts together: http://www.amazon.com/Contract-Versioning-Prentice-Service-Oriented-Computing/dp/013613517X
- A Smoother Change to Version 2.0 by Marc De Graauw http://www.xml.com/pub/a/2007/04/11/a-smoother-change-to-version-20.html
About Author:
Sergey Beryozkin is a Principal Engineer working for Progress Fuse Open Source Division and is an Apache CXF committer. Sergey has been interested in web services and XML technologies for many years. He blogs at http://sberyozkin.blogspot.com
Opinions expressed by DZone contributors are their own.
Comments