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.