CDI Conversations for JAX-RS with JEE 6

RESTful services are a great basis for client sided JavaScript WebApps whether written in jQuery UI or AngularJS among others. However browsers can only associate server responses to the correct tab if the request is unique, otherwise if the site is open in multiple tabs the response may be routed to any one of the open tabs.

The routing issue can be solved by simply adding an arbitrary tab specific unique parameter to the request that allows the browser to route the response to the correct tab.

../resources/stuff?tabUniqueId=1

This works as long as we don’t want to maintain any tab specific server sided state. CDI Conversations offer and easy and ready made solution to this problem. When dealing with JSF applications it is a simple matter of dealing with conversations, since the entire handling is performed by the respective CDI and JSF implementation.

When dealing with plain JavaScript applications communicating with RESTful services on the server side, we have to handle the conversation management ourself.

For this to work with JAX-RS and CDI 1.0 (JEE6), we have to initialize the conversation context for each request. This can be achieved with a filter. Since this is not part of the CDI 1.0 specification, it has to be implemented specifically for the particular CDI implementation, the example below is based on Weld (full source).

    ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final String[] cids = (String[]) request.getParameterMap().get("cid");

        final HttpConversationContext conversationContext = getHttpConversationContext();
        conversationContext.associate((HttpServletRequest) request);

        if (cids == null || cids.length == 0 || cids[0] == null || cids[0].length() == 0) {
            //Activate new conversation.
            conversationContext.activate();
        } else {
            //Reactivate existing conversation.
            conversationContext.activate(cids[0]);
        }

        chain.doFilter(request, response);
        conversationContext.invalidate();
        conversationContext.deactivate();
    }

    private HttpConversationContext getHttpConversationContext() {
        return Container.instance().deploymentManager().instance().select(HttpConversationContext.class).get();
    }
    ...

As you can see it obtains the HttpConversationContext from the Weld Container and associates it with the HttpServletRequest. Depending on weather a cid has been provided or not it either activates a new conversation context or re-activates an existing conversation.

The filter is redundant for JEE7 compliant application servers since a CDI Conversation Filter has become part of the CDI 1.1 specification.

From here on it is a simple matter of starting a conversation and retrieving the cid to be used by all future requests.
Since we have initialized the CDI conversation context, we can manage the conversations via the RESTful service.

@ConversationScoped
@Path("/conversationwizard")
public class ConversationWizard implements Serializable {

    @Inject
    private Conversation conversation;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String startConversation() {
        if (conversation.isTransient()) {
            conversation.begin();
        }
        return conversation.getId();
    }

    @DELETE
    public void endConversation() {
        conversation.end();
    }
}

We are using HTTP GET to start a long running conversation and return the associated conversation ID, whilst an HTTP DELETE ends it. The returned cid will then have to be propagated on the client side to all future AJAX requests, otherwise the request cannot be associated to the correct conversation.

To start a long running conversation it is necessary to initiate a GET request to the conversation wizard. Since the conversations cid is necessary for all further conversation scoped requests to the server, this request should be a blocking call stopping all further interactions until the request has been completed.

This can be achieved with jQuery, using a synchronous AJAX call (no pun intended, see code below).

var data = {cid: ''};
$.ajax({
         url: '/basic-angularjs-ee/resources/conversationwizard',
         async : false
       })
       .done(function(d) {
         data.cid = d;
       })

The cid can then be attached to all further requests, this will allow the browser to route the response to the correct tab and if desired keep conversational state on the server side.

$.ajax({ url: '/basic-angularjs-ee/resources/message?cid=' + data.cid}).done(function(){...});

Please note, you will have to limit the conversation filter to servlets that do not support AsyncContext. This is down to the fact that by starting an AsyncContext, the actual HTTP request thread has been released and along with it the conversation context.

I have created a basic AngularJS JEE 6 sample application for demonstration purposes. The sources are available on github.

Acknowledgements

I would like to thank Björn Sonntag and Robert Meyer for reviewing my posts.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.