I have a Seam 3 view-scoped, stateful EJ bean, which is used from JSF pages (you can skip the code on the first read):
@Named
@ViewScoped
@Stateful
@TransactionAttribute( TransactionAttributeType.SUPPORTS )
@LocalBean
public class CollisionManager implements ActionListener, Serializable
{
...
@Inject
private CollisionService collisionService;
// selected collisions (must be an array, call 911-PRIMEFACES)
private Collision[] selectedCollisionsArray;
private List<Collision> selectedCollisions;
public Collision getSelectedCollision()
{
if ( this.selectedCollisionsArray == null || this.selectedCollisionsArray.length == 0 )
{
return null;
}
return this.selectedCollisionsArray[0];
}
public void setSelectedCollision( Collision selectedCollision )
{
this.selectedCollisionsArray = new Collision[1];
this.selectedCollisionsArray[0] = selectedCollision;
}
/**
* Called by <f:actionListener>
*
* This throws an exception:
* java.lang.IllegalStateException: Transaction is not active in the current thread.
* ...
*/
@Override
public void processAction( ActionEvent event )
{
this.log.infov( "Calling single-selection CLASS NAME action listener for {0}", this.selectedCollisionsArray[0] );
reloadSelectedCollisions();
}
/**
* Called by <f:setPropertyActionListener>
*
* This throws no exception.
*/
public void setReloadCollision( @SuppressWarnings( "unused" ) Collision selectedCollision )
{
this.log.infov( "Calling single-selection PROPERTY action listener for {0}", this.selectedCollisionsArray[0] );
reloadSelectedCollisions();
}
public void reloadSelectedCollisions()
{
this.selectedCollisions = ...; // reload list for at least one selected collision
}
public void setSelectedStateChangeCollision( Collision selectedStateChangeCollision )
{
this.selectedStateChangeCollision = selectedStateChangeCollision;
}
}
We have a list of items (collisions) in which multiple items can be selected via checkbox. There's a dedicated button for each item to edit a single collision.
Here's the multi-selection JSF command button at the top:
<p:commandButton icon="ui-icon ui-icon-flag"
value="Change state"
disabled="#{empty collisionManager.selectedCollisionsArray}"
actionListener="#{collisionManager.reloadSelectedCollisions}"
process="@this"
update=":state-change-dialog"
oncomplete="stateChangeDialog.show();">
<f:setPropertyActionListener target="#{collisionManager.selectedStateChangeCollision}" value="#{collisionManager.selectedCollisions[0]}" />
</p:commandButton>
There are no problems with this button.
The multi-select elements are known at the point this button is pressed (edit dialog shown) due to the user having selected the items before invoking the dialog.
For single-selection buttons in the column, I first used the following, very similar command button:
<p:commandButton icon="ui-icon ui-icon-flag"
actionListener="#{collisionManager.reloadSelectedCollisions}"
process="@this"
update=":state-change-dialog :collision-form:list"
oncomplete="stateChangeDialog.show();">
<f:setPropertyActionListener target="#{collisionManager.selectedCollision}" value="#{cln}" />
<f:setPropertyActionListener target="#{collisionManager.selectedStateChangeCollision}" value="#{cln}" />
</p:commandButton>
This of course fails, because a click on the button would first call collisionManager.reloadSelectedCollisions()
before the current entity was set via the <f:setPropertyActionListener
.
I then removed the command button actionListener
attribute and tried a "class name" action listener to solve the ordering problem:
<p:commandButton icon="ui-icon ui-icon-flag"
process="@this"
update=":state-change-dialog :collision-form:list"
oncomplete="stateChangeDialog.show();">
<f:setPropertyActionListener target="#{collisionManager.selectedCollision}" value="#{cln}" />
<f:actionListener type="de.company.project.CollisionManager" />
<f:setPropertyActionListener target="#{collisionManager.selectedStateChangeCollision}" value="#{cln}" />
</p:commandButton>
For the processAction( ActionEvent event )
method to work, I had to add the interface to the CollisionManager
bean and annotate it with @LocalBean
. You can see the final class as posted above.
However, this throws an exception:
java.lang.IllegalStateException: Transaction is not active in the current thread.
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.validateTransactionManager(JavaEETransactionManagerJTSDelegate.java:447)
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.setRollbackOnlyDistributedTransaction(JavaEETransactionManagerJTSDelegate.java:344)
at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.setRollbackOnly(JavaEETransactionManagerSimplified.java:971)
at com.sun.enterprise.transaction.UserTransactionImpl.setRollbackOnly(UserTransactionImpl.java:257)
at org.jboss.seam.transaction.UTTransaction.setRollbackOnly(UTTransaction.java:82)
at org.jboss.seam.transaction.DefaultSeamTransaction.setRollbackOnly(DefaultSeamTransaction.java:104)
at org.jboss.seam.faces.transaction.UnexpectedExceptionObserver.processEvent(UnexpectedExceptionObserver.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.jboss.weld.util.reflection.SecureReflections$13.work(SecureReflections.java:267)
at org.jboss.weld.util.reflection.SecureReflectionAccess.run(SecureReflectionAccess.java:52)
at org.jboss.weld.util.reflection.SecureReflectionAccess.runAsInvocation(SecureReflectionAccess.java:137)
at org.jboss.weld.util.reflection.SecureReflections.invoke(SecureReflections.java:263)
at org.jboss.weld.introspector.jlr.WeldMethodImpl.invokeOnInstance(WeldMethodImpl.java:170)
at org.jboss.weld.introspector.ForwardingWeldMethod.invokeOnInstance(ForwardingWeldMethod.java:51)
at org.jboss.weld.injection.MethodInjectionPoint.invokeOnInstanceWithSpecialValue(MethodInjectionPoint.java:154)
at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:245)
at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:233)
at org.jboss.weld.event.ObserverMethodImpl.notify(ObserverMethodImpl.java:213)
at org.jboss.weld.manager.BeanManagerImpl.notifyObservers(BeanManagerImpl.java:590)
at org.jboss.weld.manager.BeanManagerImpl.fireEvent(BeanManagerImpl.java:580)
at org.jboss.weld.manager.BeanManagerImpl.fireEvent(BeanManagerImpl.java:575)
at org.jboss.seam.faces.event.SystemEventBridge.processEvent(SystemEventBridge.java:69)
at org.jboss.seam.faces.event.DelegatingSystemEventListener.processEvent(DelegatingSystemEventListener.java:51)
at javax.faces.event.SystemEvent.processListener(SystemEvent.java:106)
at com.sun.faces.application.ApplicationImpl.processListeners(ApplicationImpl.java:2168)
at com.sun.faces.application.ApplicationImpl.invokeListenersFor(ApplicationImpl.java:2144)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:302)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:246)
at javax.faces.application.ApplicationWrapper.publishEvent(ApplicationWrapper.java:670)
at javax.faces.application.ApplicationWrapper.publishEvent(ApplicationWrapper.java:670)
at org.jboss.seam.faces.environment.SeamApplicationWrapper$Proxy$_$$_WeldClientProxy.publishEvent(SeamApplicationWrapper$Proxy$_$$_WeldClientProxy.java)
at com.sun.faces.lifecycle.Phase.queueException(Phase.java:160)
at com.sun.faces.lifecycle.Phase.queueException(Phase.java:149)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:104)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at de.company.project.filter.encoding.EncodingFilter.doFilter(EncodingFilter.java:62)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at com.ocpsoft.pretty.PrettyFilter.doFilter(PrettyFilter.java:126)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at org.jboss.solder.servlet.exception.CatchExceptionFilter.doFilter(CatchExceptionFilter.java:65)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at org.jboss.solder.servlet.event.ServletEventBridgeFilter.doFilter(ServletEventBridgeFilter.java:74)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at de.company.project.filter.security.LoginFilter.doFilter(LoginFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
at java.lang.Thread.run(Thread.java:722)
As a patch, I tried an <f:setPropertyActionListener>
, passing the current list entity:
<p:commandButton icon="ui-icon ui-icon-flag"
process="@this"
update=":state-change-dialog :collision-form:list"
oncomplete="stateChangeDialog.show();">
<f:setPropertyActionListener target="#{collisionManager.selectedCollision}" value="#{cln}" />
<f:setPropertyActionListener target="#{collisionManager.reloadCollision}" value="#{cln}" />
<f:setPropertyActionListener target="#{collisionManager.selectedStateChangeCollision}" value="#{cln}" />
</p:commandButton>
This OTOH does not throw an exception. This really makes me wonder...
Q:
Why is this working with a <f:setPropertyActionListener>
(calling method setReloadCollision( Collision selectedCollision )
), but it fails with <f:actionListener />
(calling method processAction( ActionEvent event )
)?
Note, that I tried @TransactionAttribute( TransactionAttributeType.REQUIRED )
and @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW )
in all variants on both of the listener methods without success.
Help is highly appreciated.
Addendum:
- Who is supposed to know this?
- How are "average" Java EE developers supposed to know?
- Where is such information hidden?
Thanks