Propogating Conversations with JAX-WS Client
mattdavies Jul 7, 2009 10:33 PMI spent the better part of a day beating my head into the desk as I could not get the my code to propogate a conversation. I followed the seambay example and set up my web services exactly like the examples. I published my seam app to a JBoss server, and I used wsconsume to generate the stubs and service calls.
I could call the methods, but I could not force seam to start the conversation. I could log via soap, and the system would remember my Identity, but the conversation-scoped bean didn't remember me.
The answer lies with the jax-ws client that I was using. I found that I not only needed to ensure the session was active, but I also needed to put the conversationId provided by the first invocation of my service into the soap headers.
So here is the client code in all its glory
Client Code:
import javax.xml.namespace.QName; import javax.xml.ws.BindingProvider; import javax.xml.ws.Service; import org.domain.soapstuff.TestService; import org.domain.soapstuff.TestService_Service; import com.sun.xml.ws.api.message.Header; import com.sun.xml.ws.api.message.HeaderList; import com.sun.xml.ws.api.message.Headers; import com.sun.xml.ws.developer.JAXWSProperties; import com.sun.xml.ws.developer.WSBindingProvider; public class Tester { public void testsoap () { // TODO Auto-generated method stub System.out.println("Starting testsoap"); try { long count = 1000; //Generate the services TestService_Service ceservice = new TestService_Service(); TestService cservice = ceservice.getPort(TestService.class); // Set to keep the session active ((BindingProvider)cservice).getRequestContext().put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true); /* * Login * Then get and set the conversationId from the responseContext */ System.out.println("logging in: " + cservice.login("matt","matt")); setConversationId( getConversationId(cservice), cservice); /* * Simple loop to ensure that my test method maintains and propogates the conversation */ long start = System.currentTimeMillis(); for (long i = 0; i < count; i++ ) cservice.testMethod(); long end = System.currentTimeMillis(); System.out.println("Elapsed: " + (end-start) + " " + ((double)(end-start) / (double) count) + " " + ((double)1000/( (double)(end-start) / (double) count)) + " / sec" ); /* * Now test logging out. * Logout invalidates the Identity, ends the conversation, and also destroys the session for good measure */ cservice.logout(); /* * Do it all again to ensure it works good. */ System.out.println("logging in: " + cservice.login("matt","matt")); setConversationId( getConversationId(cservice), cservice); for (long i = 0; i < 2; i++ ) cservice.testMethod(); cservice.logout(); System.out.println("Done"); } catch (Exception e) { e.printStackTrace(); } System.out.println("Stopping testsoap"); } public String getConversationId(TestService service) { String conversationId = ""; /* * Get the conversationId from the responseContext * */ try { HeaderList hl = (HeaderList)((BindingProvider)service).getResponseContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY); for (Header h : hl) { if (h.getLocalPart().equals("conversationId")) conversationId = h.getStringContent(); } } catch (Exception e) { e.printStackTrace(); } return conversationId; } public void setConversationId(String conversationId, TestService service) { try { /* * This is the magic * this sets the soap header with the conversation id */ WSBindingProvider bp = (WSBindingProvider) service; bp.setOutboundHeaders( Headers.create(new QName("http://www.jboss.org/seam/webservice", "conversationId", "seam"),conversationId) ); } catch (Exception e) { e.printStackTrace(); } } }
And the Web Service Code:
@Stateless @Name("testBean") @WebService(name = "TestService", serviceName = "TestService") @WebContext(contextRoot="t/TestService") public class TestBeanImpl implements TestBean { @Logger Log log; @In(create = true, value="entityManager") private EntityManager em; @Resource WebServiceContext wsCtxt; private String getRemoteIP () { MessageContext msgCtxt = wsCtxt.getMessageContext(); HttpServletRequest req = (HttpServletRequest)msgCtxt.get(MessageContext.SERVLET_REQUEST); String clientIP = req.getRemoteAddr(); return clientIP; } private String getSessionID () { MessageContext msgCtxt = wsCtxt.getMessageContext(); HttpServletRequest req = (HttpServletRequest)msgCtxt.get(MessageContext.SERVLET_REQUEST); String clientIP = req.getSession().getId(); return clientIP; } @WebMethod public boolean login(String username, String password) { Identity.instance().getCredentials().setUsername(username); Identity.instance().getCredentials().setPassword(password); Identity.instance().login(); log.info("Logging in " + username + " " + password); getAction().start(); return Identity.instance().isLoggedIn(); } @WebMethod public boolean logout() { getAction().endConversation(); Identity.instance().logout(); MessageContext msgCtxt = wsCtxt.getMessageContext(); HttpServletRequest req = (HttpServletRequest)msgCtxt.get(MessageContext.SERVLET_REQUEST); req.getSession().invalidate(); return !Identity.instance().isLoggedIn(); } @WebMethod public void getSessionId() { log.info( getSessionID() ); } @WebMethod @Restrict("#{identity.loggedIn}") public int testMethod() { TestBeanAction action = (TestBeanAction)Component.getInstance( "testBeanAction",true ); int count = action.hello(); action.start(); return count; } private TestBeanAction getAction() { return (TestBeanAction) Component.getInstance( "testBeanAction",true ); } }
Hope this helps someone out there.