Serializing reference of @Stateful session beans in EJB 3.1 with GlassFish 3.1

Z Jacek Laskowski - Wiki Projektanta Java EE

It's all started with the EJB 2.1 and the javax.ejb.Handle interface. I remember I've always been meaning to check it out myself - how it is to save a reference of a stateful session EJB. There's a myriad of use cases that would justify the need to figure it out - storing a @Stateful EJB reference in a HTTP session that can be serialized - saved to a byte stream - and send over a network to a remote server instance or a remote GUI client that stores a reference to a bean and once it's up again restores it to continue the EJB session, i.e. use the state on a server. There may also be a use case for passing a reference on to another client, possibly on a different JVM.

The matter is all about serializing a reference of a @Stateful session bean.

The current EJB version is 3.1 with 3.0 in between. The interface is still in the EJB 3.1 specification, but it's merely for pre-EJB 3.0 developments (backward-compatibility). You can use the interface but I guess you'll surely not. The question has thus remained and with no need to implement any interface or extend a superclass to implement a @Stateful bean it was mysterious even more.

Spis treści

@Stateful session bean

The @Stateful session bean consists of a business interface as a separate interface type and its implementation.

Business Interface - pl.japila.ejb.Hello

This is the business interface - pl.japila.ejb.Hello.

package pl.japila.ejb;
 
public interface Hello {
    String sayHello(String whom);
}

@Stateful Implementation - pl.japila.ejb.impl.HelloStateful

This is the implementation as a stateful session bean - pl.japila.ejb.impl.HelloStateful. The state is kept in the whoms list and keeps track of whom a greeting has been given.

package pl.japila.ejb.impl;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.ejb.Stateful;
 
import pl.japila.ejb.Hello;
 
@Stateful
public class HelloStateful implements Hello {
 
    List<String> whoms = new ArrayList<String>();
 
    @Override
    public String sayHello(String whom) {
        whoms.add(whom);
        return String.format("Hello, %s statefully! [size: %d]", whom, whoms.size());
    }
 
}

JUnit test with embedded GlassFish 3.1 - HelloStatefulTests

With the HelloStateful EJB, I wrote a junit test with an embedded instance of GlassFish 3.1.

package pl.japila.ejb;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.naming.NamingException;
 
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
 
public class HelloStatefulTests {
 
    static EJBContainer ejbContainer;
    static Context ctx;
 
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // Włączenie śledzenia wykonania GlassFish'a
        Logger.getLogger("").getHandlers()[0].setLevel(Level.FINEST);
        Logger.getLogger("javax.enterprise.system.tools.deployment").setLevel(Level.FINEST);
 
        // Uruchomienie wbudowanego kontenera EJB 3.1
        ejbContainer = EJBContainer.createEJBContainer();
 
        // Dostanie się do kontekstu JNDI
        ctx = ejbContainer.getContext();
    }
 
    @Test
    public void testHelloStateful() throws Exception {
 
        Object object = ctx.lookup("java:global/classes/HelloStateful");
 
        assertNotNull(object);
        assertTrue(object instanceof Hello);
 
        Hello hello = (Hello) object;
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 1]", output);
        }
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 2]", output);
        }
 
        // save the stateful bean instance
        // serialize the instance to a file
        // http://java.sun.com/javase/technologies/core/basic/serializationFAQ.jsp#tree
        final String serializedInstanceFilename = "stateful.ejb";
        {
            FileOutputStream ostream = new FileOutputStream(serializedInstanceFilename);
            ObjectOutputStream p = new ObjectOutputStream(ostream);
            p.writeObject(hello);
            p.flush();
            ostream.close();
        }
        hello = null;
 
        // ask GC to evict handles from memory
        System.gc();
 
        // creates new stateful instance - initial state
        hello = (Hello) ctx.lookup("java:global/classes/HelloStateful");
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 1]", output);
        }
 
        // read the serialized instance
        FileInputStream istream = new FileInputStream(serializedInstanceFilename);
        ObjectInputStream q = new ObjectInputStream(istream);
        hello = (Hello) q.readObject();
        {
            String output = hello.sayHello("Agatka");
 
            // execute business method on a previously used instance
            // state maintained?
            assertEquals("Hello, Agatka statefully! [size: 3]", output);
        }
 
    }
 
    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        try {
            if (ctx != null) {
                ctx.close();
            }
        } catch (NamingException ex) {
            // handle error
        }
        if (ejbContainer != null) {
            ejbContainer.close();
        }
    }
 
}

java.io.NotSerializableException: com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate

I found a discussion about the same topic in EJB 3.0 - Stateful... getHandle()?. It was not exactly about my problem - it was about a HTTP session and a EJB reference, but made me think about the possible ways to handle it. It was exactly when I thought a EJB reference is likely java.io.Serializable already.

I run the test. It failed with the following stacktrace:

java.io.NotSerializableException: com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
	at pl.japila.ejb.HelloStatefulTests.testHelloStateful(HelloStatefulTests.java:65)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

The exception was due to the reference object not being fully serializable.

I found a couple of discussions where the exception was mentioned, e.g. Serialization problem with CDI, EJB and Proxies, albeit they didn't propose a solution.

java.lang.ClassCastException: Object is not of remote type java.rmi.Remote

Since the exception was thrown for local (default visibility when no @Remote or @Local annotations are used - see 4.9.7 Session Bean's Business Interface in the EJB 3.1 specification, page 124) business interface, I hoped it would not happen for @Remote business interface.

I changed the bean to be @Remote.

package pl.japila.ejb.impl;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.ejb.Remote;
import javax.ejb.Stateful;
 
import pl.japila.ejb.Hello;
 
@Stateful
@Remote
public class HelloStateful implements Hello {
 
    List<String> whoms = new ArrayList<String>();
 
    @Override
    public String sayHello(String whom) {
        whoms.add(whom);
        return String.format("Hello, %s statefully! [size: %d]", whom, whoms.size());
    }
 
}

I run the test again and the other exception was thrown:

javax.naming.NamingException: Lookup failed for 'java:global/classes/HelloStateful'
in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory,
java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming}
[Root exception is javax.naming.NamingException: ejb ref resolution error for remote business interfacepl.japila.ejb.Hello
[Root exception is java.lang.ClassCastException]]
	at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:518)
	at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:455)
	at javax.naming.InitialContext.lookup(InitialContext.java:392)
	at javax.naming.InitialContext.lookup(InitialContext.java:392)
	at pl.japila.ejb.HelloStatefulTests.testHelloStateful(HelloStatefulTests.java:43)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: javax.naming.NamingException: ejb ref resolution error for remote business interfacepl.japila.ejb.Hello
[Root exception is java.lang.ClassCastException]
	at com.sun.ejb.EJBUtils.lookupRemote30BusinessObject(EJBUtils.java:434)
	at com.sun.ejb.containers.RemoteBusinessObjectFactory.getObjectInstance(RemoteBusinessObjectFactory.java:75)
	at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:304)
	at com.sun.enterprise.naming.impl.SerialContext.getObjectInstance(SerialContext.java:556)
	at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:514)
	... 29 more
Caused by: java.lang.ClassCastException
	at com.sun.corba.ee.impl.javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:262)
	at javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:137)
	at com.sun.corba.ee.impl.presentation.rmi.DynamicMethodMarshallerImpl$12.read(DynamicMethodMarshallerImpl.java:353)
	at com.sun.corba.ee.impl.presentation.rmi.DynamicMethodMarshallerImpl.readResult(DynamicMethodMarshallerImpl.java:483)
	at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.privateInvoke(StubInvocationHandlerImpl.java:203)
	at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:152)
	at com.sun.corba.ee.impl.presentation.rmi.codegen.CodegenStubBase.invoke(CodegenStubBase.java:227)
	at com.sun.ejb.codegen._GenericEJBHome_Generated_DynamicStub.create(com/sun/ejb/codegen/_GenericEJBHome_Generated_DynamicStub.java)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at com.sun.ejb.EJBUtils.lookupRemote30BusinessObject(EJBUtils.java:422)
	... 33 more
Caused by: java.lang.ClassCastException: Object is not of remote type java.rmi.Remote
	at com.sun.corba.ee.impl.javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:254)
	... 45 more

The exception was already reported as GLASSFISH-15775 Remote EJBs fail with ClassCastException in embeddable Glassfish.

org.omg.CORBA.BAD_OPERATION: The delegate has not been set!

I changed the test's setUpBeforeClass so the call to InitialContext was after the ContextClassLoader was properly set.

@BeforeClass
public static void setUpBeforeClass() throws Exception {
    // Włączenie śledzenia wykonania GlassFish'a
    Logger.getLogger("").getHandlers()[0].setLevel(Level.FINEST);
    Logger.getLogger("javax.enterprise.system.tools.deployment").setLevel(Level.FINEST);
 
    // Uruchomienie wbudowanego kontenera EJB 3.1
    ejbContainer = EJBContainer.createEJBContainer();
 
    // Dostanie się do kontekstu JNDI
    Thread.currentThread().setContextClassLoader(HelloStatefulTests.class.getClassLoader()); 
    ctx = ejbContainer.getContext();
}

I run the test again that yielded yet another exception.

javax.ejb.EJBException: java.rmi.RemoteException: CORBA BAD_OPERATION 0 No; nested exception is: 
	org.omg.CORBA.BAD_OPERATION: The delegate has not been set!  vmcid: 0x0  minor code: 0  completed: No
	at pl.japila.ejb._Hello_Wrapper.sayHello(pl/japila/ejb/_Hello_Wrapper.java)
	at pl.japila.ejb.HelloStatefulTests.testHelloStateful(HelloStatefulTests.java:87)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.rmi.RemoteException: CORBA BAD_OPERATION 0 No; nested exception is: 
	org.omg.CORBA.BAD_OPERATION: The delegate has not been set!  vmcid: 0x0  minor code: 0  completed: No
	at com.sun.corba.ee.impl.javax.rmi.CORBA.Util.mapSystemException(Util.java:311)
	at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:136)
	at com.sun.corba.ee.impl.presentation.rmi.codegen.CodegenStubBase.invoke(CodegenStubBase.java:227)
	at pl.japila.ejb.__Hello_Remote_DynamicStub.sayHello(pl/japila/ejb/__Hello_Remote_DynamicStub.java)
	... 27 more
Caused by: org.omg.CORBA.BAD_OPERATION: The delegate has not been set!  vmcid: 0x0  minor code: 0  completed: No
	at org.omg.CORBA.portable.ObjectImpl._get_delegate(ObjectImpl.java:53)
	at com.sun.corba.ee.spi.presentation.rmi.StubAdapter.getDelegate(StubAdapter.java:175)
	at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerImpl.invoke(StubInvocationHandlerImpl.java:134)
	... 29 more

It turned out that the reference was indeed serialized to a file, but after deserialization the reference was incorrect.

java.io.NotSerializableException: com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate

As I couldn't find any traces of the problem in the Internet, I changed the bean to use local business interface - I removed the @Remote annotation.

package pl.japila.ejb.impl;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.ejb.Stateful;
 
import pl.japila.ejb.Hello;
 
@Stateful
public class HelloStateful implements Hello {
 
    List<String> whoms = new ArrayList<String>();
 
    @Override
    public String sayHello(String whom) {
        whoms.add(whom);
        return String.format("Hello, %s statefully! [size: %d]", whom, whoms.size());
    }
 
}

No other changes were made and I rerun the test.

java.io.NotSerializableException: com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
	at pl.japila.ejb.HelloStatefulTests.testHelloStateful(HelloStatefulTests.java:66)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

The exception was what I'd already seen. I found the thread redeploy on glassfishv3-tp2 and GLASSFISH-5192 [V3] Redeploy failing with java.io.NotSerializableException that was marked as Fixed. Although it was for a web tier, I begun hoping I found a solution.

SUCCESS

And I did find a solution, albeit a GlassFish-specific one - note the use of the com.sun.ejb.containers.JavaEEObjectStreamHandlerForEJBs and com.sun.enterprise.container.common.spi.util.* classes.

They saved my day, but it's not an acceptable solution for the other JEE6 application servers out there. Is there a better way in GlassFish 3.1? Drop me an email to jacek@japila.pl.

package pl.japila.ejb;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.naming.NamingException;
 
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
 
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;
 
public class HelloStatefulTests {
 
    static EJBContainer ejbContainer;
    static Context ctx;
 
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // Włączenie śledzenia wykonania GlassFish'a
        Logger.getLogger("").getHandlers()[0].setLevel(Level.FINEST);
        Logger.getLogger("javax.enterprise.system.tools.deployment").setLevel(Level.FINEST);
 
        // Uruchomienie wbudowanego kontenera EJB 3.1
        ejbContainer = EJBContainer.createEJBContainer();
 
        // Dostanie się do kontekstu JNDI
        Thread.currentThread().setContextClassLoader(HelloStatefulTests.class.getClassLoader());
        ctx = ejbContainer.getContext();
    }
 
    @Test
    public void testHelloStateful() throws Exception {
 
        Object object = ctx.lookup("java:global/classes/HelloStateful");
 
        assertNotNull(object);
        assertTrue(object instanceof Hello);
 
        Hello hello = (Hello) object;
 
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 1]", output);
        }
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 2]", output);
        }
 
        // save the stateful bean instance
        // serialize the instance to a file
        // http://java.sun.com/javase/technologies/core/basic/serializationFAQ.jsp#tree
        final String serializedInstanceFilename = "stateful.ejb";
        {
            FileOutputStream ostream = new FileOutputStream(serializedInstanceFilename);
            Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<JavaEEObjectStreamHandler>();
            handlers.add(new JavaEEObjectStreamHandlerForEJBs());
            JavaEEObjectOutputStream p = new JavaEEObjectOutputStream(ostream, true, handlers);
            p.writeObject(hello);
            p.flush();
            ostream.close();
        }
        hello = null;
 
        // ask for GC so handles are cleared
        System.gc();
 
        // creates new stateful instance - initial state
        hello = (Hello) ctx.lookup("java:global/classes/HelloStateful");
        {
            String output = hello.sayHello("Agatka");
            assertEquals("Hello, Agatka statefully! [size: 1]", output);
        }
 
        // read the serialized instance
        FileInputStream istream = new FileInputStream(serializedInstanceFilename);
        Collection<JavaEEObjectStreamHandler> handlers = new ArrayList<JavaEEObjectStreamHandler>();
        handlers.add(new JavaEEObjectStreamHandlerForEJBs());
        JavaEEObjectInputStream q = new JavaEEObjectInputStream(istream, this.getClass().getClassLoader(), true, handlers);
        hello = (Hello) q.readObject();
        {
            String output = hello.sayHello("Agatka");
 
            // execute business method on a previously used instance - state
            // maintained
            assertEquals("Hello, Agatka statefully! [size: 3]", output);
        }
 
    }
 
    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        try {
            if (ctx != null) {
                ctx.close();
            }
        } catch (NamingException ex) {
            // handle error
        }
        if (ejbContainer != null) {
            ejbContainer.close();
        }
    }
 
}
Osobiste