Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

Seam applications make heavy use of stateful session beans (SFSB's) for managing conversational state. I ran into a nasty exception/stack trace recently and the root cause of the problem was improper handling of non-serializable and non-transient fields prior to passivation by the EJB container.

In order for a SFSB to be successfully passivated, all instance variables must either be primitive values or objects that are serializable (i.e. classes that implement the java.io.Serializable interface). There are also some special types like javax.ejb.SessionContext and javax.jta.UserTransaction which do not need to be serializable and may be passivated and activated by the EJB container successfully.

When a bean is about to be passivated, a method on the bean class may be annotated with @PrePassivate to receive a callback for this event. This can be used to alert the bean instance that it is about to enter the Passivated state. At this time, the bean instance should close any open resources and set all nontransient, nonserializable fields to null. This prevents problems from occurring when the bean is serialized. Transient fields are simply ignored.

source: Burke EJB3 O'Reilly book

In the following example, JBoss 4.2.1.GA and Seam 2.0.0.GA were used. There is a single facelet, one SFSB, and one Java bean (POJO). To keeps things simple, there are no database CRUD operations.

Note that in the SFSB implementation class, there is a private Map field named params in the SFSB. In the java.util.Map API note that the Map interface does not extend the java.io.Serializable interface.

So what happens here is once you click the submit button in the JSF and wait for around 10 min's (presumably based on JBoss EJB container's LRU eviction policy) for the EJB container to attempt to passivate the SFSB, in the server.log you will see a very long stack trace with multiple exceptions starting with:

20:33:59,946 ERROR [TestPassivate] problem passivation thread
javax.ejb.EJBException: Could not passivate; failed to save state
	at org.jboss.ejb3.cache.simple.StatefulSessionFilePersistenceManager.passivateSession(StatefulSessionFilePersistenceManager.java:406)
	at org.jboss.ejb3.cache.simple.SimpleStatefulCache.passivate(SimpleStatefulCache.java:301)
	at org.jboss.ejb3.cache.simple.SimpleStatefulCache$SessionTimeoutTask.run(SimpleStatefulCache.java:209)

and ending with:

Caused by: java.lang.NoClassDefFoundError: org/jaxen/VariableContext
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
	at java.lang.Class.getDeclaredMethod(Unknown Source)
	at org.jboss.serial.classmetamodel.ClassMetaData.lookupMethodOnHierarchy(ClassMetaData.java:102)
	at org.jboss.serial.classmetamodel.ClassMetaData.lookupInternalMethods(ClassMetaData.java:432)
	at org.jboss.serial.classmetamodel.ClassMetaData.<init>(ClassMetaData.java:122)
	at org.jboss.serial.classmetamodel.ClassMetamodelFactory.getClassMetaData(ClassMetamodelFactory.java:350)
	at org.jboss.serial.objectmetamodel.ObjectDescriptorFactory.describeObject(ObjectDescriptorFactory.java:168)
	at org.jboss.serial.objectmetamodel.DataContainer$DataContainerDirectOutput.writeObject(DataContainer.java:206)
	at org.jboss.serial.persister.ArrayPersister.saveObjectArray(ArrayPersister.java:110)
	at org.jboss.serial.persister.ArrayPersister.writeData(ArrayPersister.java:101)
	at org.jboss.serial.objectmetamodel.ObjectDescriptorFactory.describeObject(ObjectDescriptorFactory.java:276)
	at org.jboss.serial.objectmetamodel.DataContainer$DataContainerDirectOutput.writeObject(DataContainer.java:206)
	at org.jboss.serial.persister.RegularObjectPersister.writeSlotWithFields(RegularObjectPersister.java:182)
	at org.jboss.serial.persister.ObjectOutputStreamProxy.writeFields(ObjectOutputStreamProxy.java:79)
	at org.jboss.serial.persister.ObjectOutputStreamProxy.defaultWriteObject(ObjectOutputStreamProxy.java:68)
	at java.util.Vector.writeObject(Unknown Source)
	... 164 more

This last exception is misleading and I have opened a JIRA issue for this. These exceptions are being thrown because the container is trying to passivate the conversational state of the SFSB and is encountering the Map params field which is not marked @Transient and is not serializable. It would be nice if the stack trace pointed out which fields specifically were the root cause(s) of the problem...

So in order to fix this problem, you must do one of the following options:

0) in the @PrePassivate method, set params to null.

1) mark the Map params field with @Transient so that the EJB container will not serialize this field during the passivation.

2) use a local variable instead of the instance variable

3) turn off passivation for this SFSB entirely

You may also want to use the @PostActivate callback method to re-initialize any transient fields.

For additional information, refer to the following sources: Section 4.2.1 of JSR220-core. Pages 245-247 of EJB 3.0 by Bill Burke

.xhtml:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	    		xmlns:ui="http://java.sun.com/jsf/facelets"
	  			xmlns:h="http://java.sun.com/jsf/html"
	  			xmlns:f="http://java.sun.com/jsf/core"
	  			xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:a="http://richfaces.org/a4j"
                xmlns:rich="http://richfaces.org/rich"
				template="template.xhtml">

<!-- content -->
<ui:define name="content">
<div class="section">
	<h1>Test Passivation Error</h1>
</div>
<div class="section">
	<div class="entry errors">
		<h:messages globalOnly="true"/>
	</div>
	

	
	<h:form> 
		<rich:dataTable var="widget" value="#{myWidgetList}" rendered="#{myWidgetList.rowCount>0}">
			<rich:column>
				 <f:facet name="header">
	                   <h:outputText value="ID" />
				 </f:facet>
				 <h:outputText value="#{widget.id}"/>
            </rich:column>
            <rich:column>
				 <f:facet name="header">
	                   <h:outputText value="Name" />
				 </f:facet>
				 <h:outputText value="#{widget.name}"/>
            </rich:column>
            <rich:column>
				 <f:facet name="header">
	                   <h:outputText value="Date" />
				 </f:facet>
				 <h:outputText value="#{widget.date}"/>
            </rich:column>
            <rich:column>
				 <f:facet name="header">
	                   <h:outputText value="Desc" />
				 </f:facet>
				 <h:outputText value="#{widget.desc}"/>
            </rich:column>
		</rich:dataTable>
		<h:commandButton value="submit" action="#{testPassivate.submit}"/>
	</h:form>
	
</div>
</ui:define>



</ui:composition>

SFSB:

@Stateful
@Scope(ScopeType.SESSION)
@Name("testPassivate")
public class TestPassivate implements TestPassivateLocal {
	
	@Logger
	private Log log;
	
	private Map params;
	
	@DataModel
	private List<Widget> myWidgetList;
	
	@Factory("myWidgetList")
	public void getWidgets(){
		log.info("in getWidgets");
		myWidgetList = buildWidgetList();
	}
	
	public void submit() {
		log.info("begin foobar()");
		params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();		
	}
	
	private List buildWidgetList() {
		Widget widget1 = new Widget(1, "widget1", new Date(), "This is my first widget");
		Widget widget2 = new Widget(2, "widget2", new Date(), "This is my 2nd widget");
		Widget widget3 = new Widget(3, "widget3", new Date(), "This is my 3rd widget");
		Widget widget4 = new Widget(4, "widget4", new Date(), "This is my 4th widget");
		Widget widget5 = new Widget(5, "widget5", new Date(), "This is my 5th widget");
		
		List<Widget> myList = new ArrayList<Widget>();
		myList.add(widget1);
		myList.add(widget2);
		myList.add(widget3);
		myList.add(widget4);
		myList.add(widget5);
		
 		return myList;
	}
	
	@PrePassivate
	private void prePassivate() {
		log.info("in prePassivate");
	}
	
	@PostActivate
	private void postActivate() {
		log.info("in postActivate");
	}
	
	@Destroy @Remove
	public void destroy(){}
}

SFSB interface:

@Local
public interface TestPassivateLocal {
	public void getWidgets();
	public void submit() ;
	public void destroy();
}

Java Bean:

public class Widget implements Serializable {
	
	private Integer id;
	private String name;
	private Date date;
	private String desc;
	
	Widget(Integer id, String name, Date date, String desc) {
		this.id = id;
		this.name = name;
		this.date = date;
		this.desc = desc;
	}
	
	public void setId(Integer id){
		this.id = id;
	}
	
	public Integer getId() {
		return id;
	}
	
	public void setName(String name){
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public void setDate(Date date){
		this.date = date;
	}
	
	public Date getDate() {
		return date;
	}
	
	public void setDesc(String desc){
		this.desc = desc;
	}
	
	public String getDesc() {
		return desc;
	}
}