Friday, July 25, 2008

Rest and Soap united in CXF

Please ignore the fact that Rest and Soap are presented as two alternatives - even though it's not very accurate this just helps me to make a shorter title for this blog entry.

I was quite interested in a 'Restifying' SOAP-based services idea awhile back, especially after a Web Method Feature was introduced. I'm not sure anyone relies on it today, when writing practical SOAP-based web services - I just don't know.

The arrival of JAX-RS introduces some new interesting possibilities in this area - from the perspective of the server-side application development at least.

Consider the ever popular Bank-Account scenario which uses a Factory pattern.
Here's a typical JAX-WS class :

@WebService
public class BankService {

public javax.xml.ws.wsaddressing.W3CEndpointReference createAccount(AccountInfo ai) {
// code omitted for brevity
}
}

This class will return EPRs, one per every new Account created.

Now consider the same class slightly updated :

@WebService(wsdlLocation="bank.wsdl")
@Path("/bank")
public class BankService {

@POST
public javax.xml.ws.wsaddressing.W3CEndpointReference createAccount(AccountInfo ai) {
// code omitted for brevity
}
}

It is the same class with JAX-WS and JAX-RS annotations mixed in.

In case of SOAP, POST createAccount requests will be targeted to, say, http://localhost:8080/bankservice endpoint address, while in the other case POST requests will be targeted to http://localhost:8080/bank address.

The only missing bit is a JAX-RS MessageBodyWriter which can handle the handling of W3CEndpointReferences :

public class W3c
MessageBodyWriter {

public void writeTo(W3CEndpointReference epr, ..., MutivaluedMap<String, Object> headers, OutputStream os) {
// just update the headers, do not write into the stream
headers.putSingle("Location", getEprAddress(epr));
}

}

Now, the problem is that status code needs to be set to 201 in this case and it's not possible to set a custom http status code in JAX-RS MessageBodyWriters. Enter CXF JAX-RS filters. They will be invoked before the headers and status code is set on a CXF private Message class :

public class EprResponseHandler implements ResponseHandler {
public Response handleResponse(Message m,
OperationResourceInfo invokedOperation,
Response response) {
Object entity = response.getEntity();
if (entity != null
&& W3CEndpointReference.class.isAssignableFrom(entity)) {
return Response.created(getEndpointAddress(entity)).build();
} else {
return null;
}
}
}

Note, the need for a custom MessageBodyWriter goes away now.

Finally, one needs to configure both JAX-WS and JAX-RS endpoints using Spring for ex, and you're done.

A couple of additional notes. There's really no need to go for all this trouble of creating custom filters unless you'd like to preserve the same interface you've already used before starting to play with JAX-RS - this may not be that far-fetched a requirement at all. And sorry for writing a lame and incomplete code - hopefully it still makes sense :-)

Finally, note how very similar the programming model on the server can be, when using both JAX-WS and JAX-RS. Big Web Services are often compared to Corba for a number of reasons, one of them the use of EPRs - this is definitely a red-herring. It's the non-support for GET and fine-grained interfaces which put the sides apart.

2 comments:

Anderson Nielson said...

Any example on how to deploy a CXF Rest WS in SMX 4?

Chris Cooper said...

Sergey,

In order to get a POST to work for both REST and SOAP, you can't return
a "Response" object and instead return "void".

With CXF, is there a way to access the Response object and set the status along with the new uri to access the resource just added without having it as a return parameter and without using ExceptionResolver?