Destroying conversations and killAllOtherConversations
fuzzy333 Oct 1, 2009 7:27 PMSeam 2.1.2 - JBoss 4.2.3 - Hibernate 3.3.2
I've got a requirement to invalidate all conversations that contain a specific context variable when a particular action is invoked. I've almost been able to figure out how to do this by looking at Manager.killAllOtherConversations. I can remove the conversation no sweat. What I can't figure out is how to properly destroy the conversation context, which Manager.killAllOtherConversations doesn't seem (haha pun) to do either:
In killAllOtherConversations(), an attempt is made to destroy the context by calling Lifecycle.destroyConversationContext() with getSessionMap(). The getSessionMap() method returns a map of the session variables named by Contexts.getSessionContext().getNames() which doesn't include any conversation names (like \"org.jboss.seam.CONVERSATION\#123$someContextItem\"). So when it comes around to destroying individual context items, no conversation items are found, and hence none destroyed.
Now in my code if I modify this slightly and call Lifecycle.destroyConversationContext(FacesContext.getCurrentInstance().getExternalContext().getSessionMap(), conversationEntry.getId()), then the conversation context items do get found and destroyed, yay! But! An exception is thrown when the entityManager is destroyed:
10:28:33,500 WARN [org.jboss.seam.Component] Exception calling component @Destroy method: entityManager java.lang.IllegalStateException: attempting to destroy the persistence context while an active transaction exists (try installing <transaction:ejb-transaction/>) at org.jboss.seam.persistence.ManagedPersistenceContext.close(ManagedPersistenceContext.java:214) at org.jboss.seam.persistence.ManagedPersistenceContext.destroy(ManagedPersistenceContext.java:177) 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.jboss.seam.util.Reflections.invoke(Reflections.java:22) at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144) at org.jboss.seam.Component.callComponentMethod(Component.java:2249) at org.jboss.seam.Component.callDestroyMethod(Component.java:2180) at org.jboss.seam.Component.destroy(Component.java:1469) at org.jboss.seam.contexts.Contexts.destroy(Contexts.java:251) at org.jboss.seam.contexts.Contexts.destroyConversationContext(Contexts.java:413) at org.jboss.seam.contexts.Lifecycle.destroyConversationContext(Lifecycle.java:175)
Looking at the code in this stack trace, I see that ManagedPersistenceContext.close() barfs this error when Transaction.instance().isActive() == true, of course in this case it is active! But the transaction that is active is for the current request, and has nothing to do with the entityManager that is getting destroyed! grrrr... I'm not too familiar with seam's transaction management so I don't know if there is a good way around this. The commit that added this check is 5488 with the message Some EJBs can be BMTs...
which doesn't get me anywhere.
For reference, here is the action method that does all of this:
private void clearPatientConversations() { /* * mostly copied from org.jboss.seam.core.Manager.killAllOtherConversations() */ String currentCID = Conversation.instance().getId(); List<ConversationEntry> entries = new ArrayList<ConversationEntry>(ConversationEntries.instance().getConversationEntries()); for( ConversationEntry conversationEntry : entries ) { if( currentCID.equals( conversationEntry.getId() ) ) { log.debug( "skipping current conversation.id: " + currentCID ); continue; } boolean success = Manager.instance().switchConversation( conversationEntry.getId() ); if( !success ) { throw new RuntimeException( "oops" ); } if( !Conversation.instance().isLongRunning() ) { continue; } for( String s : Contexts.getConversationContext().getNames() ) { if( s.equals( "patientConversation" ) ) { log.info( "Destroying patient conversation..." ); boolean locked = conversationEntry.lockNoWait(); try { Lifecycle.destroyConversationContext(FacesContext.getCurrentInstance().getExternalContext().getSessionMap(), conversationEntry.getId()); ConversationEntries.instance().removeConversationEntry(conversationEntry.getId()); } finally { if( locked ) { conversationEntry.unlock(); } } log.info( "Detroyed patient conversation" ); } } } Manager.instance().switchConversation( currentCID ); }
So two items need some attention, and I only really care about the second item to get my requirement in finished at this point:
1 - Manager.killAllOtherConversations doesn't destroy the conversation contexts
2 - ManagedPersistenceContext.destroy fails when a transaction is active
Are these bugs? Any suggested workarounds? For the moment I'm going to try to register the conversations to be destroy and have some sort of callback run after the current transaction is closed to do the actual destroying.
Thanks in advance!
Jonathan