Thursday, February 26, 2009

CXF JAXRS Client API updates

We've been working on improving the initial Client API prototype we had added to CXF. Particularly, the goal was to tap a bit into a powerful CXF core runtime which gives us interceptor chains and a highly optimized HTTPConduit.

I was overwhelmed with how many options CXF JAXWS client runtime had for all sorts of invocations, asynchronous ones, one ways, synchronous ones with retries, automatic processing of cases where a destination has been moved to a new address, secure HTTPS invocations, etc. So CXF JAXRS client runtime will eventually end up being as flexible and configurable as its JAXWS 'brother' (hopefully, if you're completely into REST then this analogy does not shock you too much :-)).

With CXF 2.2 coming out soon I didn't have time to update the CXF JAXRS client code to do all the things CXF JAXWS can do - this will gradually be done later on. But few key things have been done, mainly it's now possible for both proxy and http-centric CXF JAXRS clients to transparently pick up the CXF Bus configuration, rely on the HTTPConduit which deals with all the complicated HTTP stuff (chunking, http proxies, timeouts, etc), as well as custom outbound and inbound CXF interceptors.
One interesting thing about it is that you can now basically reuse in certain cases, say, secure JAXWS and JAXRS proxies (with the HTTPS-related configuration being picked up from Spring) and you can always find out what type of proxy you're dealing with :



BookService jaxwsService = ...
BookService jaxrsService = ...
useBookService(jaxwsService);
useBookService(jaxrsService);

if (WebClient.client(jaxwsService) == null) {
// it's a jaxws client
}



For example, see this JAXRS HTTPS test which does a secure invocation with both proxy and http-centric clients. Note how straightforward it is. Actually that needs to be simplified, with a CXF Bus creation code to be pushed into a JAXRSClientFactoryBean - but you can do it right now by simply passing a command line CXF property pointing to a configuration code with the bus creation code becoming redundant.

The actual simple configuration is here, it configures both client and server sides.

Another interesting thing is that we can now inject JAXRS proxies into the server service code, be it JAXWS or JAXRS one. For example, have a look at this test resource class which can serve both JAXRS and JAXWS invocations. JAXRS proxy is injected like this :


public class BookStoteSoapRestImpl {
@Resource(name="rectClient")
private BookStoreJaxrsJaxws webClient;
}


and then it's used inside a getBook() method, irrespectively of whether it's a jaxws or jaxrs invocation which is inderway, by invoking the same method :


public class BookStoteSoapRestImpl {
public Book getBook(Long id) {
// if it's not a recursive invocation then
webClient.getBook();
}
}


Now the invocation goes over HTTP in this case but we'll add the support for local transport invocations too.

Please check this configuration sample on how a jaxrs:client is configured. Among other things you can setup all the headers which need to flow, plus input/output interceptors and whether subresource proxies if any need to inherit the headers. I'll also add a support for a basic autorization here as opposed to doing it at the Client level, as one really needs to do it in combination with HTTPS.

You can also inject jaxws:clients into your JAXRS endpoints if you wish. REST and SOAP united indeed.

So that's what happening with CXF JAXRS Client API. More enhancements will be done to it, to its proxy, http- and xml-centric parts and the way it integrates with the core runtime.

Stay tuned.

10 comments:

Unknown said...

Hi Sergey,

I was playing with the client API recently. It is really impressive.
I was getting the below exception when I called client.delete() on the resource:

java.net.ProtocolException: HTTP method DELETE doesn't support output
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:822)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleHeadersTrustCaching(HTTPConduit.java:1881)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1902)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:66)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:611)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)

The resource was deleted just fine, but looks like the Conduit is trying to get the outputstream resulting in this exception.

Do you know why this is happening?

Appreciate any help.

-Arul

Sergey Beryozkin said...

thanks Arul - I'll have a look. I agree, HttpConduit is the culprit here

cheers, Sergey

Unknown said...

Thanks Sergey.

In case if you need a test case, I recently blogged about the CXF 2.2 JAXRS & JAXWS services development (http://aruld.info/cxf-22-in-action-services-design-simplified/) which can provide some more details.

Invoking deleteEntry() can reproduce this problem for you.

-Arul

Sergey Beryozkin said...

Fixed now, thanks a million for the feedback

Unknown said...

I just built the trunk and tested it. It works cool. Thanks for fixing it so quickly.

-Arul

Vincenzo Vitale said...

Hi,

thanks for the post.

I'm experiencing a problem when a call through the client is done for the service:

@Path("/query-service")
@Produces("application/xml")
@Consumes("textn/text")
public interface TridionQueryService {

/**
* Execute a generic query in the Tridion system through a POST request.
*
* @param query The query to be executed.
* @param channel The channel where to execute the given query. If the
* channel is not passed a default value (different depending on the
* implementation) will be used.
* @return A {@link Response} object containing the xml response received
* from Tridion..
*/
@POST
@Path("/execute")
@Produces("application/xml")
Response executeQuery(@FormParam("query") String query,
@FormParam("channel") String channel);

the remote service is calling fine but than on the client I get a WebApplicationException and looking at the stack trace, it seems there is no Message Handler defined.

I create the client with the code:
tridionQueryServiceProxy=JAXRSClientFactory.create(getTridionProxyBaseAddress(),TridionQueryService.class);

and than:
Response tridionProxyResponse =tridionQueryServiceProxy.debugExecuteQuery(...);


Any clue on this?



Thanks,
Vincenzo

Vincenzo Vitale said...

A typo in the previous post:

Response tridionProxyResponse =tridionQueryServiceProxy.ExecuteQuery(...);
without debug in the method...


V.

Vincenzo Vitale said...

Solved. I was forgetting to define a new custom provider to be used since the Response class is not handled automatically.

Than I registered it with:
ProviderFactory.getSharedInstance().registerUserProvider(new ResponseProvider());


V.

Sergey Beryozkin said...

Hi Vincenzo

A better option would be to do

JAXRSClientFactory.create(
"http://bar", TridionQueryService.class, listOfProviders);

You can also use JAXRSClientFactoryBean directly if no appropriate helper method is found on JAXRSClientFactory

thanks, Sergey

Vincenzo Vitale said...

Hi Sergey,

thanks for the suggestion. It worked fine and it's absolutely cleaner and safer.

I used this because I was planning to have basic authentication on the server and there isn't a constructor with both credentials and providers.


Ciao,
Vincenzo.


P.s.: A little out of scope message... sorry for that but we are trying to promoe the project. Check out the cargo-itest project for a simpler integration test setup:
http://code.google.com/p/cargo-itest/