views:

301

answers:

4

I have an RPC service in GWT that needs to return a List. The List can be filled with various types of objects, all of which are serializable and all of are referenced elsewhere in my service so they should be available to GWT RPC. However, unless I put on a generic type parameter (e.g. ArrayList<String>), GWT gives me the warning:

Return type: java.util.ArrayList
    java.util.ArrayList
      Verifying instantiability
         java.util.ArrayList
            [WARN] Checking all subtypes of Object which qualify for serialization`
Adding '465' new generated units

Essentially, I just want a way to declare List or ArrayList without GWT trying to generate code for every serializable object on the class path. Isn't there some way to tell GWT that I know what I'm doing and not to go crazy?

+3  A: 

If you add an ArrayList or similarly an Object field to a serializable object, the GWT compiler has no choice but to include all possible variants in its compilation. You are essentially declaring I can send anything using this field , so the compiler makes sure that you are able to send anything.

The solution is to declare , using generic parameters, the specific types you are sending. That might require splitting into multiple parameters or classes, but it does keep the code size and compile time down.

Robert Munteanu
I understand what it is trying to do, but from searching around this is a widely confusing thing. Why does GWT not give me finer grained control over what I consider Serializable? Under what circumstances would the current behavior ever be useful? I can't imagine a situation where it would be acceptable to just blindly pull in every type.
Sean
I think it's quite hard to do this correctly from a GWT point of view. If you have an idea on how to accomplish it I suggest you raise an issue in the GWT tracker : http://code.google.com/p/google-web-toolkit/issues/entry
Robert Munteanu
+1  A: 

You will have to help GWT by being very precise in what you return. A typical solution is to use a root class or marker interface and declare that the RPC method returns an ArrayList, then GWT can trim down the possible types.

David Nouls
+3  A: 

Let me expand on what David Nouls said. The GWT compiler can't read your mind, so when you fail to specify what the return types can be, GWT assumes that it can be anything, and has to do extra work to make sure that can happen on the Javascript client side.

You really should specify what types are able to be returned. There is only upside to doing this--as the compiler will produce more optimized code, rather than generating code to handle '465 genreated units', so your downloads will be faster.

I would suggest creating an empty interface called "BaseResult" and then having the objects you return all implement that that interface.

/**
 * Marker interface 
 */
public interface BaseResult {
}

Then you specify that the return type of your rpc method is ArrayList:

public interface MyRpcService extends RemoteService {
  public ArrayList<BaseResult> doRpc();
}

Then make sure your return objects all implement that interface.

public class UserInfo implements BaseResult {}
public class Order implements BaseResult {}

Now the GWT compiler will have a much easier time optimizing for your code.

filsa
This seems like a reasonable approach, with the downside that it won't work for something like Integer, or String. I think what I'm trying to do is a valid use case. I have an API on the server which I expose through EJB, SOAP, AMF, and GWT-RPC. There's a "batch" method on the API that will take multiple method names and argument lists and executes all of the API calls on the server via reflection in one round trip to the server and in a transactional way. Unfortunately since the methods can take any parameters and since they can return anything I can't declare a strong generic type.
Sean
A: 

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);
    }
}
Jeff Woodward
@Robert - I should have probably been more clear on leveraging this technique to your specific problem. There are a number of ways; you could for example mimic the tricks used by RpcObject in some way creating a custom ArrayList subclass... But, the point of the solution was to use RpcObject *instead* of ArrayList as the type of the method parameter or field type in the transfer object, then "wrap" your "generic" ArrayList (that isn't java-generics parameterized) via the RpcObject.setValue() method. As long as all the values in your ArrayList are serializable by GWT, you should be all set.
Jeff Woodward