It is less than desirable to have the GWT compiler build type serializers for everything under the sun; in the worst case, it fails entirely because, for example, there can be a class (from let's say a third-party GWT library that you are using) that declares a type in the "client" package that implements java.io.Serializable. Should you attempt to use that type in your code, it becomes part of the classpath that the GWT compiler analyzes to build a type serializer for; however, at runtime the class isn't part of the classpath on the server because the type was defined in the "client" package and therefore not compiled for the server! RPC calls, whether they attempt to use that specific type or not, fail with a ClassNotFound exception. Perfect!
It is also, as the poster articulated, impossible to make existing primitive types implement some marker interface whether it be IsSerializable or a custom marker interface (such as BaseResult as suggested above).
Nonetheless, a solution is needed! So here is what I came up with:
1) Use IsSerializable (or some subclass of it) rather than using java.io.Serializable on all your custom transfer objects.
2) Use the following implementation of RpcObject in those instances where you need a generic object type to hold a value that you know will be GWT-RPC serializable (whether it be one of your custom transfer objects that implements IsSerializable or a more "primitive" type such as java.lang.String [see the comments in the RpcObject implementation below for those types that have been whitelisted] that GWT already knows how to serialize!)
This solution is working for me...it both keeps GWT from building type-serializers for every java.io.Serializable class under the sun while at the same time allows me as the developer to transfer values around using a single/uniform type for primitives (that I can't add the IsSerializable marker interface to) as well as my own custom IsSerializable transfer objects. Here is an example of using RpcObject (although using it is so simple, I feel a bit strange about including such examples):
RpcObject rpcObject = new RpcObject();
rpcObject.setValue("This is a test string");
Thanks to the java-generics trickery of the getValue() method, casting can be kept to a minimum, so to retrieve the value (whether it be on the client or the server), you can just do the following without any need for a cast:
String value = rpcObject.getValue();
You can just as easily transfer one of your custom IsSerializable type:
CustomDTO customDto= new CustomDTO(); // CustomDTO implements IsSerializable
customDto.setYourProperty(to_some_value);
RpcObject rpcObject = new RpcObject();
rpcObject.setValue(customDto);
And again, later on the client or server, the value can be fetched easily (without casting):
CustomDTO customDto = rpcObject.getValue();
You can just as easily wrap something such as java.util.ArrayList:
List list = new ArrayList(); // Notice: no generics parameterization needed!
list.add("This is a string");
list.add(10);
list.add(new CustomDTO());
RpcObject rpcObject = new RpcObject();
rpcObject.setValue(list);
And once again, later in either the client or server code, you can get the List back with:
List list = rpcObject.getValue();
After looking at the "white-listing" in RpcObject, you may be inclined to think that only List<String>
would have been white-listed; you would be wrong ;-) As long as all the values added to the List
are IsSerializable or objects of types from the JRE that GWT-RPC just knows how to serialize, then you'll be all set. However, if you do need to white-list additional types, for example a type from a third-party library that used java.io.Serializable instead of IsSerializable may need to be individually white-listed (see the implementation of RpcObject for details), they can be added as new fields directly in RpcObject, or to keep the overhead lower in the common cases, add them to a subclass of RpcObject and use the subclass only when needed (since it is a subclass, none of your client or server method signatures would need to change from using the generic RpcObject type).
I am using this strategy to solve problems nearly identical to those described by the original poster. I hope that other may find it a useful technique as well, but as always, your mileage may vary... If the school of GWT thought has advanced beyond this technique, please comment and let me know!
-Jeff
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
public class RpcObject implements IsSerializable {
protected HashMap<String, IsSerializable> rpcObjectWrapper = new HashMap<String, IsSerializable>();
/*
* NOTE: The following fields are here to
* trick/fool/work-around/whatever-you-want-to-call-it GWT-RPC's
* serialization policy. Having these types present, even though their
* corresponding fields are never used directly, enables GWT-RPC to
* serialize/deserialize these primitive types if they are encountered in
* the rpcWrapperObject! Of course GWT-RPC already knows how to serialize
* all these primitive types, but since, for example, String doesn't
* implement GWT's IsSerializable interface, GWT has no expectation that it
* should ever be allowed in the rpcWrapperObject instance (and thus String,
* as well as all the other Java primitives plus Arrays of such types as
* well as List, Set, and Map, won't be part of the serialization policy of
* the RpcObject type). This is unfortunate because thanks to java type
* erasure, we can easily stuff Strings, Integers, etc into the wrapper
* without any issues; however, GWT-RPC will cowardly refuse to serialize
* them. Thankfully, it appears that the serialization policy is for the
* RpcObject type as a whole rather than for the rpcObjectWrapper field
* specifically. So, if we just add some dummy fields with these "primitive"
* types they will get added to the serialization policy (they are
* effectively white-listed) of the type as a whole, and alas, GWT-RPC stops
* cowardly refusing to serialize them.
*/
protected Byte _byte;
protected Short _short;
protected Integer _integer;
protected Long _long;
protected Float _float;
protected Double _double;
protected Date _date;
protected Boolean _boolean;
protected Byte[] _bytes;
protected Short[] _shorts;
protected Integer[] _integers;
protected Long[] _longs;
protected Float[] _floats;
protected Double[] _doubles;
protected Date[] _dates;
protected Boolean[] _booleans;
protected List<String> _list;
protected Set<String> _set;
protected Map<String, String> _map;
public RpcObject() {
super();
}
@SuppressWarnings("unchecked")
public <X> X getValue() {
HashMap h = (HashMap) rpcObjectWrapper;
X value = (X) h.get("value");
return value;
}
@SuppressWarnings("unchecked")
public void setValue(Object value) {
HashMap h = (HashMap) rpcObjectWrapper;
h.put("value", value);
}
}