Wednesday, July 11, 2012

Glassfish 3 (JavaEE 6) Serialization issues with Wicket.


I wanted to use wicket-cdi with Glassfish to take full advantage a full Java EE 6 server and still use wicket for ui development.  I ran into a Serialization issue using EJBs.  Weld is the reference implementation used by Glassfish and Weld creates proxy classes for the EJBs.  EE 6 is nice because you just create concrete classes for Services, no need for interfaces.Adam Bien explains this in detail here.
 http://www.adam-bien.com/roller/abien/entry/how_to_deal_with_interfaces

However Wicket's default Serializer , JavaSerializer, will automatically throw an exception if a class is not Serializable or not a recognized java.lang.reflect.Proxy. Weld's magic classes are neither of these so the Wicket Serializer fails.  Below is some steps to get you to my problem, and a solution.


To Integrate Wicket with Glassfish to allow for CDI  use Igor Vaynberg's wicket-cdi module
     https://github.com/42Lines/wicket-cdi
Note I downloaded latest snapshot and edited the necessary classes to make it work with wicket 6, but the same should apply to Wicket 1.5 and you will not need to recompile wicket-cdi

In your Application file override init and add the following lines.
BeanManager beanManager = 
(BeanManager) new InitialContext()
         .lookup("java:comp/BeanManager");
            
            new CdiConfiguration(beanManager)
.setPropagation(ConversationPropagation.NONBOOKMARKABLE)
.configure(this);

Now create a  Stateless Bean


@Stateless
public class HelloService {

   public String sayHello() {
      return "Yet another Hello World";
   }
}


Create a WicketPage

public class HomePage extends WebPage {

    @Inject
    HelloService helloService;

    public HomePage() {
        
        add(new Label("hello",helloService.sayHello()));
    }
}

Notice that I used @Inject to inject my Stateless EJB. This is because wicket-cdi enabled CDI (JSR-299) support.


Once you launch the app and see the label on the screen,  go look at the server log.

At this point you get a printstacktrace due to Wicket's JavaSerializer not being able to serialize the Stateless Service.
   Caused by: java.io.NotSerializableException:   com.example.__EJB31_Generated__HelloService__Intf____Bean__

JavaSerializer does excellent job at creating a message to log the issue.
Field hierarchy is:
  14 [class=com.example.HomePage, path=14]
    com.example.HelloService com.example.HomePage.helloService [class=com.example.HelloService$Proxy$_$$_Weld$Proxy$]
      javassist.util.proxy.MethodHandler com.example.HelloService$Proxy$_$$_Weld$Proxy$.methodHandler [class=org.jboss.weld.bean.proxy.ProxyMethodHandler]
        private org.jboss.weld.bean.proxy.BeanInstance org.jboss.weld.bean.proxy.ProxyMethodHandler.beanInstance [class=org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance]
          private final javassist.util.proxy.MethodHandler org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance.methodHandler [class=org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler]
            private final org.jboss.weld.ejb.api.SessionObjectReference org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler.reference [class=org.glassfish.weld.ejb.SessionObjectReferenceImpl]
              private java.lang.Object org.glassfish.weld.ejb.SessionObjectReferenceImpl.ejbRef [class=com.example.__EJB31_Generated__HelloService__Intf____Bean__] <----- field that is not serializable


 Adding Serializable to HelloService will not fix the issue because Weld disregards the marker interface, and that is fine by me because there is no requirement in the EJB standard that the Stateless EJB needs to be Serializable.

Solution:

Create a custom Wicket ISerializer.  I found an example http://jaceklaskowski.pl/wiki/Serializing_reference_of_@Stateful_session_beans_in_EJB_3.1_with_GlassFish_3.1
And applied his solution. Note the code below is Java 1.7 so you will have to edit the errors for 1.6


import com.sun.ejb.containers.JavaEEObjectStreamHandlerForEJBs;
import com.sun.enterprise.container.common.spi.util.JavaEEObjectInputStream;
import com.sun.enterprise.container.common.spi.util.JavaEEObjectOutputStream;
import com.sun.enterprise.container.common.spi.util.JavaEEObjectStreamHandler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.wicket.serialize.ISerializer;

/** Allows for Glassfish Proxy objects to be serialized and deserialized when using CDI
 *
 * @author jsarman
 */
public class EJBSerializer implements ISerializer {

    private ISerializer fallback;

    public EJBSerializer(ISerializer fallback) {
        //TODO Add Null Check here
        this.fallback = fallback;
    }

    @Override
    public Object deserialize(byte[] data) {
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<>();
            handlers.add(new JavaEEObjectStreamHandlerForEJBs());
            JavaEEObjectInputStream q = 
                    new JavaEEObjectInputStream(bais, 
                    getClass().getClassLoader(), true, handlers);
            return q.readObject();            
        } catch (IOException | ClassNotFoundException ex) {
            return fallback.deserialize(data);
        }
    }

    @Override
    public byte[] serialize(Object object) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<>();
            handlers.add(new JavaEEObjectStreamHandlerForEJBs());
            JavaEEObjectOutputStream oos = 
                    new JavaEEObjectOutputStream(baos, true, handlers);
            oos.writeObject(object);            
            return baos.toByteArray();
        } catch (IOException e) {            
            return fallback.serialize(object);
        }
    }
}


finally add the following code to Application init
getFrameworkSettings()
          .setSerializer(
           new EJBSerializer(
             getFrameworkSettings().getSerializer()));


This replaces Wickets Serializer with the custom Serializer.  The code also uses the original Serializer when a exception is raised so that nice logging capabilities of JavaSerializer is not lost.

Once the app is reloaded in Glassfish the Serialization problems go away.  Don't forget to go to another page then click the back button to verify the deserializer works as well.

Sorry this is only for Glassfish, but I am sure that the same basic principle applies to other JEE 6 servers that have this issue.

Update:
I updated Glassfish using update tool today (7/19/2012) which broke the EJBSerializer due to some refactoring in the Glassfish common-container.jar. I redesigned the EJBSerializer to dynamically load the refactored classes and replace the now missing JavaEEObjectStreamHandlerForEJBs class. The code dynamically finds the IndirectlySerializable and  SerializableObjectFactoryClass, so this should support Glassfish 3.1.2 with and without updates.     




 import com.sun.enterprise.container.common.spi.util.JavaEEObjectInputStream;  
 import com.sun.enterprise.container.common.spi.util.JavaEEObjectOutputStream;  
 import com.sun.enterprise.container.common.spi.util.JavaEEObjectStreamHandler;  
 import java.io.ByteArrayInputStream;  
 import java.io.ByteArrayOutputStream;  
 import java.io.IOException;  
 import java.util.ArrayList;  
 import java.util.Collection;  
 import org.apache.wicket.serialize.ISerializer;  
 /**  
  * Allows for Glassfish Proxy objects to be serialized and deserialized when  
  * using CDI  
  *  
  * @author jsarman  
  */  
 public class EJBSerializer implements ISerializer {  
   private ISerializer fallback;  
   public EJBSerializer(ISerializer fallback) {  
     this.fallback = fallback;  
   }  
   @Override  
   public Object deserialize(byte[] data) {  
     try {  
       ByteArrayInputStream bais = new ByteArrayInputStream(data);  
       Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<>();  
       handlers.add(getHandler());  
       JavaEEObjectInputStream q =  
           new JavaEEObjectInputStream(bais,  
           getClass().getClassLoader(), true, handlers);  
       return q.readObject();  
     } catch (Exception e) {          
       return fallback.deserialize(data);  
     }  
   }  
   @Override  
   public byte[] serialize(Object object) {  
     try {  
       ByteArrayOutputStream baos = new ByteArrayOutputStream();  
       Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<>();  
       handlers.add(getHandler());  
       JavaEEObjectOutputStream oos =  
           new JavaEEObjectOutputStream(baos, true, handlers);  
       oos.writeObject(object);  
       return baos.toByteArray();  
     } catch (Exception e) {          
       return fallback.serialize(object);  
     }  
   }  
   private JavaEEObjectStreamHandler getHandler() throws Exception {  
     return new GlassfishStreamHandler();  
   }  
   public static class GlassfishStreamHandler implements JavaEEObjectStreamHandler {  
     private Class indirectlySerializableClass;  
     private Class serializableObjectFactoryClass;  
     public GlassfishStreamHandler() throws ClassNotFoundException {         
       try {  
         indirectlySerializableClass = Class.forName("com.sun.ejb.spi.io.IndirectlySerializable");  
         serializableObjectFactoryClass = Class.forName("com.sun.ejb.spi.io.SerializableObjectFactory");  
       } catch (ClassNotFoundException cnfe) {  
         indirectlySerializableClass = Class.forName("com.sun.enterprise.container.common.spi.util.IndirectlySerializable");  
         serializableObjectFactoryClass = Class.forName("com.sun.enterprise.container.common.spi.util.SerializableObjectFactory");  
       }  
     }  
     @Override  
     public Object replaceObject(Object obj)  
         throws IOException {  
       Object result = obj;  
       try {  
         if (indirectlySerializableClass.isAssignableFrom(obj.getClass())) {  
           result = indirectlySerializableClass.getMethod("getSerializableObjectFactory", new Class[]{}).invoke(obj, new Object[]{});
         }  
       } catch (Exception e) {  
         e.printStackTrace();  
       }  
       return result;  
     }  
     @Override  
     public Object resolveObject(Object obj)  
         throws IOException {  
       Object result = obj;  
       try {  
         if (serializableObjectFactoryClass.isAssignableFrom(obj.getClass())) {
           result = serializableObjectFactoryClass.getMethod("createObject", new Class[]{}).invoke(obj, new Object[]{});
         }  
       } catch (Exception e) {  
         e.printStackTrace();  
       }  
       return result;  
     }  
   }  
 }