Here's my solution facing that issue:
First extend RemoteServiceServlet
(all your service servlets will extend from this new class, remember this is server code). This is the relevant code in there:
@Override
protected SerializationPolicy doGetSerializationPolicy(
HttpServletRequest request, String moduleBaseURL, String strongName) {
SerializationPolicy sp = super.doGetSerializationPolicy(request,
moduleBaseURL, strongName);
if(sp == null) { //no policy found, probably wrong client version
throw new InvalidPolicyException();
}
return sp;
}
and
@Override
protected void doUnexpectedFailure(Throwable e) {
if(e instanceof InvalidPolicyException) {
sendError(); //send message to reload client (wrong client version)
return; //that's it
}
super.doUnexpectedFailure(e);
}
The private method sendError()
is basically a customized copy from GWT 500 error code (sorry for the ugly Google indentation)
private void sendError() {
ServletContext servletContext = getServletContext();
HttpServletResponse response = getThreadLocalResponse();
try {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_CONFLICT);
try {
response.getOutputStream().write("wrong client version".getBytes("UTF-8"));
} catch (IllegalStateException e) {
// Handle the (unexpected) case where getWriter() was previously used
response.getWriter().write("wrong client version");
}
} catch (IOException ex) {
servletContext.log(
"sendError failed while sending the previous custom failure to the client", ex);
}
}
I used HttpServletResponse.SC_CONFLICT
(409) but you can probably use a clever error code. The message written is not really important.
Then in your custom RpcRequestBuilder
(this is the client code)
public class CustomRequestBuilder extends RpcRequestBuilder {
private class RequestCallbackWrapper implements RequestCallback {
private RequestCallback callback;
RequestCallbackWrapper(RequestCallback aCallback) {
this.callback = aCallback;
}
@Override
public void onResponseReceived(Request request, Response response) {
if(response.getStatusCode() == 409) { //wrong client version
Navigator.closeEveryPopUp();
Navigator.showUncloseablePopUp("Login again!",
new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
//reload your $#%^ client!!!
Window.Location.reload();
}
});
} else {
//(...)irrelevant security code here(...)
callback.onResponseReceived(request, response);
}
}
@Override
public void onError(Request request, Throwable exception) {
callback.onError(request, exception);
}
}
@Override
protected void doFinish(RequestBuilder rb) {
//(...)more irrelevant security code here(...)
super.doFinish(rb);
rb.setCallback(new RequestCallbackWrapper(rb.getCallback()));
}
}
We use multiple popups, that's one reason for the Navigator class; of course use your own style there to warn the user.
EDIT: What's going on here?
Until GWT 1.3.3 IsSerializable
was the only interface available to mark a class as GWT RPC serializabled. The next version accepted Java standard Serializable
interface for the same purpose, but adding the requirement of a security policy for objects implementing this interface. By default GWT generate a policy for each compilation with a unique hash name. An old client trying to transfer object marked as Serializable
will throw at server side a serialization policy exception that will be received at client side as a generic runtime error. IsSerializable
allows the old clients to still use the new services as long as the signature remains the same. This means that an alternate solution for this question is to mark every object going thru GWT RPC as IsSerializable
. But if for some reason you need your objects not to be referenced to a GWT interface, this is a nice solution for old clients connections.
Check GWT's guide for more details regarding the 1.3.3 fallback.