2 Replies Latest reply on Nov 8, 2013 11:23 AM by Hai-Yun Du

    Destroying conversations and killAllOtherConversations

    Jonathan Tougas Newbie

      Seam 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

        • 1. Re: Destroying conversations and killAllOtherConversations
          Jonathan Tougas Newbie

          Well thanks to everyone's input ;) I managed to get something working


          Here's the final draft. Hopefully it'll be of help to someone someday!



          public static void destroyCurrentPatientConversations() {
                    
               /*
                * inspired by org.jboss.seam.core.Manager.killAllOtherConversations()
                * related seam forum entry about problems encountered
                * http://seamframework.org/Community/DestroyingConversationsAndKillAllOtherConversations
                */
          
               String cid = Conversation.instance().getId();
               boolean isLongRunning = Conversation.instance().isLongRunning();
          
               log.debug( "current conversation (id=" + cid + ", isLongRunning=" + isLongRunning );     
          
               if( isLongRunning && ConversationEntries.instance().getConversationEntry( cid ) == null ) {
                    /* a temporary conversation does not have an entry in ConversationEntries,
                     * which makes it imposible to return to it should we switch out of it.
                     * We only need to switch back to a conversation if it is long running 
                     * (so that the redirect navigation rules properly add the cid querystring parameter)
                     * so... this shouldn't happen
                     */
                    throw new RuntimeException( "i don't exist?" );
               }
          
               List<ConversationEntry> ces = new ArrayList<ConversationEntry>(ConversationEntries.instance().getConversationEntries());
               List<String> idsToDestroy = new ArrayList<String>();
          
               for( ConversationEntry ce : ces ) {
          
                    log.debug( "switching to conversation.id: " + ce.getId() + "...");               
                    if( !Manager.instance().switchConversation( ce.getId(), false ) ) {
                         throw new RuntimeException( "failed to switch to conversation: " + ce.getId() );
                    }
          
                    boolean foundPatientConversation = false;
                    for( String s : Contexts.getConversationContext().getNames() ) {                    
                         if( s.equals( CURRENT_PATIENT  ) ) {
                              foundPatientConversation = true;
                              break;
                         }
                    }
          
                    log.debug( "is a patientConversation?" + foundPatientConversation );               
                    if( foundPatientConversation ) {
                         idsToDestroy.add( ce.getId() );
                    }
               }          
          
               // see related comment above
               if( isLongRunning && !Conversation.instance().getId().equals( cid ) ) { 
                    if( !Manager.instance().switchConversation( cid, false ) ) {
                         throw new RuntimeException( "failed to switch back to original conversation: " + cid );
                    }
               }
          
               if( idsToDestroy.size() > 0 ) {
                    /* destroy conversations after the transaction is closed,
                     * otherwise entityManager.destroy() crashes because it verifies if there is an active transaction
                     * see org.jboss.seam.persistence.ManagedPersistenceContext.close()
                     * and forum reference from above
                     */ 
                    Transaction.instance().registerSynchronization( new CurrentPatientConversationDestroyer( idsToDestroy ) );
               }
          }



          ...the control that binds the action, conversation control is relavant:



          <s:link action="#{patientSelector.clear}" propagation="#{conversation.longRunning ? 'join' : 'none'}">Clear</s:link>



          .. and related navigation rule:




          <page view-id="*">           
               <navigation from-action="#{patientSelector.clear}">
                    <redirect />
               </navigation>
          </page>