Monday, August 17, 2015

Re: An interesting issue

Well, in the light of the "don't use GWTRPC because it won't be supported in the future", I'd say to you to just use REST and forget about this stuff.

"Easy to say" - I know.

One thing that I did to avoid all the serialization problems with GWT was to create my own SerializationPolicy, allowing everything which is instance of Serializable to be... well... serializable.

I use this "hack" since GWT 2.5 I think.

Here is the code:

public class CustomSerializationPolicy extends SerializationPolicy implements TypeNameObfuscator {

   
private static final String ELISION_ERROR = "Type name elision in RPC "
           
+ "payloads is only supported if the RPC whitelist file is used.";

   
/**
     * Many JRE types would appear to be {@link Serializable} on the server.
     * However, clients would not see these types as being {@link Serializable}
     * due to mismatches between the GWT JRE emulation and the real JRE. As a
     * workaround, this blacklist specifies a list of problematic types which
     * should be seen as not implementing {@link Serializable} for the purpose
     * matching the client's expectations. Note that a type on this list may still
     * be serializable via a custom serializer.
     */

   
private static final Class<?>[] JRE_BLACKLIST = {
        java
.lang.ArrayStoreException.class, java.lang.AssertionError.class,
        java
.lang.Boolean.class, java.lang.Byte.class, java.lang.Character.class,
        java
.lang.Class.class, java.lang.ClassCastException.class,
        java
.lang.Double.class, java.lang.Error.class, java.lang.Float.class,
        java
.lang.IllegalArgumentException.class,
        java
.lang.IllegalStateException.class,
        java
.lang.IndexOutOfBoundsException.class, java.lang.Integer.class,
        java
.lang.Long.class, java.lang.NegativeArraySizeException.class,
        java
.lang.NullPointerException.class, java.lang.Number.class,
        java
.lang.NumberFormatException.class, java.lang.Short.class,
        java
.lang.StackTraceElement.class, java.lang.String.class,
        java
.lang.StringBuffer.class,
        java
.lang.StringIndexOutOfBoundsException.class,
        java
.lang.UnsupportedOperationException.class, java.util.ArrayList.class,
        java
.util.ConcurrentModificationException.class, java.util.Date.class,
        java
.util.EmptyStackException.class, java.util.EventObject.class,
        java
.util.HashMap.class, java.util.HashSet.class,
        java
.util.MissingResourceException.class,
        java
.util.NoSuchElementException.class, java.util.Stack.class,
        java
.util.TooManyListenersException.class, java.util.Vector.class};

   
private static final Set<Class<?>> JRE_BLACKSET = new HashSet<Class<?>>(
           
Arrays.asList(JRE_BLACKLIST));

   
private static final CustomSerializationPolicy sInstance = new CustomSerializationPolicy();

   
public static CustomSerializationPolicy getInstance() {
       
return sInstance;
   
}

   
/**
     * Singleton.
     */

   
private CustomSerializationPolicy() {
   
}

   
/**
     * Implemented to fail with a useful error message.
     */

   
public final String getClassNameForTypeId(String id)
           
throws SerializationException {
       
throw new SerializationException(ELISION_ERROR);
   
}

   
/**
     * Implemented to fail with a useful error message.
     */

   
public final String getTypeIdForClass(Class<?> clazz)
           
throws SerializationException {
       
throw new SerializationException(ELISION_ERROR);
   
}

   
@Override
   
public boolean shouldDeserializeFields(Class<?> clazz) {
       
return isFieldSerializable(clazz);
   
}

   
@Override
   
public boolean shouldSerializeFields(Class<?> clazz) {
       
return isFieldSerializable(clazz);
   
}

   
@Override
   
public void validateDeserialize(Class<?> clazz) throws SerializationException {
       
if (!isInstantiable(clazz)) {
           
throw new SerializationException("Type '" + clazz.getName() + "' is not serializable.");
       
}
   
}

   
@Override
   
public void validateSerialize(Class<?> clazz) throws SerializationException {
       
if (!isInstantiable(clazz)) {
           
throw new SerializationException("Type '" + clazz.getName() + "' is not serializable.");
       
}
   
}

   
/**
     * Field serializable types are primitives, {@line IsSerializable},
     * {@link Serializable}, types with custom serializers, and any arrays of
     * those types.
     */

   
private boolean isFieldSerializable(Class<?> clazz) {
       
if (isInstantiable(clazz)) {
           
return true;
       
}
       
if (Serializable.class.isAssignableFrom(clazz)) {
           
return !JRE_BLACKSET.contains(clazz);
       
}
       
return false;
   
}

   
/**
     * Instantiable types are primitives, {@line IsSerializable}, types with
     * custom serializers, and any arrays of those types. Merely
     * {@link Serializable} types cannot be instantiated or serialized directly
     * (only as super types of legacy serializable types).
     */

   
private boolean isInstantiable(Class<?> clazz) {
       
if (clazz.isPrimitive()) {
           
return true;
       
}
       
if (clazz.isArray()) {
           
return isInstantiable(clazz.getComponentType());
       
}
       
if (Serializable.class.isAssignableFrom(clazz)) {
           
return true;
       
}
       
if (IsSerializable.class.isAssignableFrom(clazz)) {
           
return true;
       
}
       
return SerializabilityUtil.hasCustomFieldSerializer(clazz) != null;
   
}
}


And the RemoteServiceServlet:

public abstract class CustomRemoteServiceServlet extends RemoteServiceServlet {

   
@Override
   
protected SerializationPolicy doGetSerializationPolicy(HttpServletRequest request, String moduleBaseURL, String strongName) {
       
return CustomSerializationPolicy.getInstance();
   
}
}


... and use it on your RPC service class:

public class MyServiceImpl extends CustomRemoteServiceServlet implements MyService {

   
// ...

}

There you go, no more serialization problems, class whitelisting, use of IsSerializable, and so on.

Hope that helps.

Gilberto

On Friday, August 14, 2015 at 9:47:02 PM UTC-3, Guillaume Rebesche wrote:
So I ran into an interesting issue today and I wanted to share it because I didn't find an explanation anywhere on the internet.
I'm not sure this issue require a fix but still it was annoying to ran into.

So today, my GWT application (GWT 2.7) didn't want to start for no obvious reason (in Debug mode or SDM). Right after the server started, I got the famous:
com.google.gwt.user.client.rpc.SerializationException: Type 'MyClass' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable'

Since I didn't have any modified files and that it was working fine 30mn before, I knew it was not a code issue. So I cleared all my caches and tried again but still no luck. I checked my deployed folder and the gwt.rpc file was there and correct.

Then I dug into GWT and found out that my application was using the LegacySerializationPolicy (hence the SerializationException).

Turns out, I had forgotten to close the browser tab of a previous SDM instance and even though I killed it from my IDE, my application was still running in the browser.
In our application we send request every 10s to check for update. So when I started my server, a request was received before the gwt.rpc file was built (and put into the deployment folder), and since GWT couldn't find this file it was using the LegacySerializationPolicy and was caching it in memory, so GWT would never use my gwt.rpc file...

Conclusion: Never forget to close the browser tab. :)

Cheers

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment