Painfully. One way is to use a custom field serializer. You should
be able to get some details on those by searching the list history for
CustomFieldSerializer.
It looks like your List<Object> is actually a list of value-types
because your examples were String, the wrapper types, and Date. One
potential work-around is to create a hierarchy of wrapper wrappers
that is serializable, like this:
public interface Wrapper extends Serializable {
Object getValue();
}
public class IntegerWrapper implements Wrapper {
private final Integer value;
public IntegerWrapper(Integer value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
// create similar types for all possible subtypes of Object that you
might put in your List
then define your DTO to have a List<Wrapper> instead of a
List<Object>. It's ugly as sin, but it allows the compiler to
eliminate unused serialization code, which is the whole reason for
disallowing List<Object> in the first place.
If you've moved to GWT 1.5, you could possibly use type parameters to
make your code a little cleaner, but there are lots of ways to use
type parameters to get yourself back in trouble so it might not be as
useful as you think.
Ian
The .gwt.rpc file is a filter for security purposes, but changing it
doesn't control what types the client is actually capable of
serializing or deserializing.
The client-side RPC code is generated. The RPC generator walks over
your service interfaces trying to figure out what types might get
transmitted over the wire. For each type that is encountered, some
purpose-built code is generated that can serialize and deserialize
instances of that type. This process is recursive because class Foo
might have a member of type Bar so, if a Foo can be transmitted, then
a Bar could be, too. The net result includes a map from type name to
a triple of functions. The triple handles serialization and
deserialization for the type it's mapped from. If you change the
.gwt.rpc file to include a type not in the map, then the server might
generate a response that the client doesn't know how to deserialize.
In that case, the client will generate the "This application is out of
date" error.
As a side issue, you mentioned that you added BigDecimal to the
.gwt.rpc file. That's not going to work no matter what you
do--BigDecimal is not supported by GWT.
Ian
Could you post some of your code? I'm interested in TestList,
Wrapper, and an implementation of Wrapper. The error you're getting
is suspicious--I'm wondering if you've tickled a bug in the RPC
generator.
As a complete shot in the dark, try moving the Serializable
declaration from Wrapper to its implementations. In other words, if
you have this:
public interface Wrapper extends Serializable
public class Foo implements Wrapper
change it to this:
public interface Wrapper
public class Foo implements Wrapper, Serializable
It could be that the RPC generator isn't recognizing the transitive
serializability of implementations of Wrapper. Another, related, shot
in the dark is to change Wrapper from an interface to an abstract
class.
> I am more interested in writing custom
> serializer because that could provide me better scalability (I think
> so, please correct me) could you please suggest me how should I start
> with that? Thanks for all the help and support.
I think the scalability of custom serializers depends on how many you
have to write. If you only have to write a handful of custom
serializers to enable the use a great number of types, then yes, they
are scalable. On the other hand, if you have to write a custom
serializer for (nearly) every type, then I think you'll get tired of
writing custom serializers really fast.
Another issue is that custom serializers won't necessarily solve your
problem because they're "opaque" to the RPC generator. I just took a
look through the issue tracker and noticed that some RPC-related
changes have gone in that I didn't notice, so my understanding of the
RPC code may be out of date, but the following was true until
recently. The RPC generator effectively assumes that types serialized
by a custom serializer are composed of only primitives. You could
write a custom serializer for a type that is composed of a
List<Object> and have it call writeObject(o) for each o in the list,
but, unless the client-side deserialization stub for o's runtime type
is created by some other means, the client won't be able to
deserialize the stream. The typical solution to that problem is to
add some methods to your service interface such that each method's
return type corresponds to a type you need to be able to deserialize
on the client. This hack tricks the RPC generator into generating the
stubs you need, but it's really ugly.
Ian
OK, I have the following, and it worked for me:
public interface Wrapper {
Object getValue();
}
public final class IntegerWrapper implements Wrapper, Serializable {
private Integer value;
private IntegerWrapper() {
}
public IntegerWrapper(Integer value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
public interface Service extends RemoteService {
List<Wrapper> getListOfWrapper();
}
public interface ServiceAsync {
void getListOfWrapper(AsyncCallback<List<Wrapper>> callback);
}
public class ServiceImpl extends RemoteServiceServlet implements Service {
public List<Wrapper> getListOfWrapper() {
return new ArrayList(Arrays.asList(new IntegerWrapper(42)));
}
}
Maybe you need to add default constructors to your serializable
classes? I think that's the only major difference between your
non-working code and my working code.
>>if you have to write a custom
>>serializer for (nearly) every type, then I think you'll get tired of
>>writing custom serializers really fast.
>
> I am little confused here, as my problem is creating a generic
> collection (for now basic java types would also do). why RPC can not
> identify that my collection has java.lang.Boolean type as RPC supports
> serialization and deserialization of Boolean values?
There are two issues here.
Perhaps most importantly, you should understand that while RPC _may_
support serializing and deserializing java.lang.Boolean (or any other
arbitrary serializable reference type), it will only do so if the RPC
generator can statically determine that it _must_ support serializing
and deserializing java.lang.Boolean instances. The reason is
multi-faceted, but mostly it comes down to the fact that client-side
GWT doesn't support reflection so the ability to (de)serialize a type
must be encoded in purpose-written code that is included in the
download. Since code size is directly correlated with at least
download speed and sometimes also runtime speed, the RPC generator
does what it can to eliminate the (de)serialization stubs for types
that it determines will not be transmitted between client and
server--why pay for the ability to (de)serialize a java.lang.Boolean
if you never transmit java.lang.Boolean instances?
The second issue to understand is that the RPC generator runs at
compile time, not runtime, so it can only look at the information
present in your source code--it can't inspect any runtime type
information. In other words, it can't look at a List<Object> and see
that it does contain java.lang.Boolean instances but doesn't contain
java.lang.Double instances. The only information present in
List<Object> is that the List could be any known serializable
implementation of the List interface and the <Object> _could_ be _any_
serializable subtype of Object. You really don't want
(de)serialization stubs for _every_ serializable subtype of Object in
your client-side code, which is why a bare List<Object> causes a
compile-time problem. You can trick the generator, though, using the
technique I suggested in my previous post--you can force the RPC
generator to generate the stubs for any arbitrary type by including a
method declaration in the service interface that returns that
arbitrary type.
Did that do anything to help your confusion?
> So if I am
> putting a custom type in my collection (like ArrayList) then I should
> provide custom serializer (even in that case implementing
> serialization should work) for that type and for java primitives or
> for all implementation present in rpc.core.java package I would expect
> RPC to do serialization for me
I'm not 100% clear on your question here, but I think you wondering
why you have to implement a custom serializer for "standard" types.
You don't. You just have to worry about the generator discovering
that, say, java.lang.Boolean is a type that will be transmitted.
Suppose you have the following code:
public class TestList {
private List<Object> list;
// constructors, accessors, etc. not important, so long as there's a
default ctor
}
At first glance, this class is not serializable because the RPC
generator will complain about the List<Object>. You can shut the
generator up by writing a custom field serializer, like so:
public class TestList_CustomFieldSerializer {
public static void serialize(SerializationStreamWriter writer,
TestList instance) throws SerializationException {
writer.writeInt(instance.getList().size());
for (Obect o : instance.getList()) {
writer.writeObject(o);
}
}
public static void deserialize(SerializationStreamReader reader,
TestList instance) throws SerializationException {
int count = reader.readInt();
for (int i = 0; i < count; ++i) {
instance.getList().add(reader.readObject());
}
}
}
The problem now is that no one knows what kinds of objects are going
to be serialized by writeObject() or deserialized by readObject(). If
it so happens that the rest of your service method declarations cause
the RPC generator to generate client-side (de)serialization stubs for
all the types that might be in a TestList's list member, then the
above code will work fine. If, on the other hand, you have exactly
one service method, defined like this:
public interface Service extends RemoteService {
TestList getTestList();
}
then you're going to run into problems. I was about to explain how to
solve this problem by writing more code in the custom field
serializer, but a posting on the contributors forum taught me
something I didn't know. You could try this:
public class TestList {
private Integer exposeIntegerType;
private String exposeStringType;
// declare other, similar fields for all the types you need, but you
don't need to reference them anywhere
private transient List<Object> list;
}
If you use the above, extended definition of TestList, I'm told you
should be able to get away with the fairly simple
TestList_CustomFieldSerializer that I outlined above.
Sorry if this email seems a little disjointed--a GWT contributor
proved me wrong part way through writing it, so I had to edit it to
better reflect reality. I'll try to answer any further questions you
might have.
Ian