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.
@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 moreThe 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(); } } }
