Stateful JAX-WS with CDI – Part 1

Usually it is desirable to keep web services stateless. This is because it allows for easier scalability (since no state has to be shared across multiple instances) and reduced load on system resources (as no state has to be held or cleaned up).

However, there are scenarios where it may be desirable to maintain server sided states across multiple requests. As I have demonstrated in my previous post CDI CONVERSATIONS FOR JAX-RS WITH JEE 6, it is possible to utilize CDI conversation scope in JAX-RS restful services. CDI conversation scope was designed to deal with the limitation of having one session per browser, allowing for tab-specific state to be kept on the server side.

JAX-WS is primarily used in fat-/smart-client server scenarios. Fat-/Smart-clients, unlike browsers, are capable of managing multiple sessions as required. JAX-WS has build-in support for SOAP and HTTP session management (see JAX-WS 2.2 Rev a, Chp. 10.4.1.4 Session Management).

Server-Side
Setting up the session context on the server side is a simple matter of defining a @SessionScoped CDI bean…

@SessionScoped
public class Interactions implements Serializable {

  private List<String> names = new ArrayList<>();

  public String interact(String name) {
    final String interactions = "Hi " + name + ", I previously interacted with " + names;
    names.add(name);
    return interactions;
  }

  public Integer getNumberOfInteractions() {
    return names.size();
  }
}

…and injecting it into the JAX-WS webservice.

@WebService
public class Interactor {
  @Inject
  private Interactions interactions;

  @WebMethod
  public String interact(String name) {
    return interactions.interact(name);
  }
}

Client-Side
The real work is on the client side, since the session management has to be performed here.

The request context of the web-service proxy can be configured to maintain the session, by setting BindingProvider.SESSION_MAINTAIN_PROPERTY to true (see JAX-WS 2.2 Rev a, 4.2.3.1 Example). This will ensure that all interactions via that web-service proxy instance will be associated with the same session.

public void sessionBoundInteraction() {
  Interactor sessionBoundInteractor = service.getInteractorPort();
  Map<String, Object> requestContext = ((BindingProvider) sessionBoundInteractor).getRequestContext();
  requestContext.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);
  ...
}

This approach works for all specified session management mechanisms defined in JSR-224.

A SOAP/HTTP binding implementation can use three HTTP mechanisms for session management:

  • Cookies To initiate a session, a service includes a cookie in a message sent to a client. The client stores the
    cookie and returns it in subsequest messages to the service.
  • URL rewriting To initiate a session a service directs a client to a new URL for subsequent interactions.
    The new URL contains an encoded session identifier.
  • SSL The SSL session ID is used to track a session.

JAX-WS 2.2 Rev a, 10.4.1.4 Session Management

To keep long running sessions the web-service proxy has to be kept in context on the client side, so it can be reused for further interaction. In a CDI client application the proxy could be kept in @SessionScoped or @ConversationScoped web-service facade for the duration of the stateful interaction.

@SessionScoped
public class MyWSServiceFacade {
  @WebService(...)
  private MyWSService service;
  
  private MyWS myWS;  

  @PostConstruct
  public void postConstruct() {
    myWS = service.getMyWSPort();
  }
}

The session is not shared with other web-service client proxy instances, nor is it available for other web-service endpoints.

The only session management mechanism that allows for session propagation across multiple web-services is cookie session management. The session cookie can be retrieved from the response context of the first interaction, which can then be stored and propagated to other JAX-WS service clients and/or new web-service proxy instances.

public class JaxWsSessionClient {

  private List sessionCookie = null;
   
  ...
  
  public void interact(String name) {
    Interactor interactor = service.getInteractorPort();
    if (cookie == null) {
      //First interaction, storing session cookie for further interactions.
      System.out.println(interactor.interact(name));
      Map<String, Object> responseContext = ((BindingProvider) interactorService).getResponseContext();
      Map<String, ?> responseHeader = (Map<String, ?>) responseContext.get(MessageContext.HTTP_RESPONSE_HEADERS);
      cookie = (List) responseHeader.get("set-cookie");
    } else {
      //Restoring session context by propagating the session cookie to the request.
      Map<String, Object> requestContext = ((BindingProvider) interactor).getRequestContext();
      requestContext.put(MessageContext.HTTP_REQUEST_HEADERS, Collections.singletonMap("cookie", cookie));
      System.out.println(interactor.interact(name));
    }
  }
  ...  
}

Both these approaches are limiting. The standard JAX-WS session management only works when using a single web service endpoint. Requiring all state-sharing @WebMethods to be aggregated into a single web service-endpoint. Whilst cookie base session management only works for one of the three available session-management mechanisms.

JBoss Seam framework provided a conversational context (not to be confused with CDI @ConversationScoped) for SOAP web services. However active development of Seam was ceased in 2012, making it a poor choice for new developments.

In part 2 I will outline the implementation of a custom CDI scope to keep server sided state independent of the employed session management and number of web service endpoints.

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 *