[seam-commits] Seam SVN: r9578 - in trunk/src: main/org/jboss/seam/persistence and 1 other directories.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Mon Nov 17 00:15:05 EST 2008


Author: dan.j.allen
Date: 2008-11-17 00:15:05 -0500 (Mon, 17 Nov 2008)
New Revision: 9578

Modified:
   trunk/src/main/org/jboss/seam/core/ConversationEntry.java
   trunk/src/main/org/jboss/seam/persistence/ManagedEntityInterceptor.java
   trunk/src/main/org/jboss/seam/persistence/ManagedEntityWrapper.java
   trunk/src/test/integration/src/org/jboss/seam/test/integration/EntityPassivationTest.java
Log:
JBSEAM-2209
note that this commit includes formatting changes (explains some of the modified lines)


Modified: trunk/src/main/org/jboss/seam/core/ConversationEntry.java
===================================================================
--- trunk/src/main/org/jboss/seam/core/ConversationEntry.java	2008-11-16 10:11:21 UTC (rev 9577)
+++ trunk/src/main/org/jboss/seam/core/ConversationEntry.java	2008-11-17 05:15:05 UTC (rev 9578)
@@ -6,6 +6,11 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.jboss.seam.Component;
+import org.jboss.seam.ScopeType;
+import org.jboss.seam.contexts.Context;
+import org.jboss.seam.contexts.Contexts;
+
 /**
  * Metadata about an active conversation. Also used
  * by the conversation list and breadcrumbs.
@@ -245,6 +250,43 @@
    {
       return conversationIdStack.size()>1;
    }
+   
+   /**
+    * Determines which conversation in the stack is holding the instance of this
+    * component. A nested conversation can see context variables in all ancestor
+    * conversations. In this case, we are interesting in knowing where that
+    * instance was found. We are assuming that if the reference is not in an
+    * ancestor conversation, then it must be in the current conversation. The
+    * goal here is not to locate the instance, but rather to determine which
+    * conversation is contributing the instance that we already know exists.
+    * 
+    * The low-level interaction with the session context should be refactored
+    * out. The problem is that it is defined in private areas of
+    * ServerConversationContext and cannot be reused. Actually, what we really
+    * need is a general purpose utility for analyzing the contents of each
+    * conversation in the stack (at least the keys).
+    */
+   public String findPositionInConversationStack(Component component)
+   {
+      if (component.isPerNestedConversation()) {
+         return id;
+      }
+      
+      String name = component.getName();
+      Context session = Contexts.getSessionContext();
+      String location = id;
+      for (int i = 1, len = conversationIdStack.size(); i < len; i++) {
+         String cid = conversationIdStack.get(i);
+         String key = ScopeType.CONVERSATION.getPrefix() + '#' + cid + '$' + name;
+         if (session.get(key) != null) {
+            location = cid;
+            break;
+         }
+      }
+      
+      return location;
+   }
+   
    @Override
    public String toString()
    {

Modified: trunk/src/main/org/jboss/seam/persistence/ManagedEntityInterceptor.java
===================================================================
--- trunk/src/main/org/jboss/seam/persistence/ManagedEntityInterceptor.java	2008-11-16 10:11:21 UTC (rev 9577)
+++ trunk/src/main/org/jboss/seam/persistence/ManagedEntityInterceptor.java	2008-11-17 05:15:05 UTC (rev 9578)
@@ -2,23 +2,14 @@
 
 import static org.jboss.seam.ScopeType.CONVERSATION;
 
-import java.io.Serializable;
-
-import org.jboss.seam.Component;
-import org.jboss.seam.ComponentType;
 import org.jboss.seam.annotations.intercept.AroundInvoke;
 import org.jboss.seam.annotations.intercept.Interceptor;
-import org.jboss.seam.annotations.intercept.PostActivate;
-import org.jboss.seam.annotations.intercept.PrePassivate;
-import org.jboss.seam.contexts.Contexts;
 import org.jboss.seam.core.BijectionInterceptor;
-import org.jboss.seam.core.Conversation;
 import org.jboss.seam.intercept.AbstractInterceptor;
 import org.jboss.seam.intercept.InvocationContext;
 import org.jboss.seam.log.LogProvider;
 import org.jboss.seam.log.Logging;
 import org.jboss.seam.transaction.Transaction;
-import org.jboss.seam.web.Session;
 
 /**
  * Swizzles entity references around each invocation, maintaining referential
@@ -69,8 +60,6 @@
       }
    }
 
-
-
    public boolean isInterceptorEnabled()
    {
       return getComponent().getScope() == CONVERSATION;

Modified: trunk/src/main/org/jboss/seam/persistence/ManagedEntityWrapper.java
===================================================================
--- trunk/src/main/org/jboss/seam/persistence/ManagedEntityWrapper.java	2008-11-16 10:11:21 UTC (rev 9577)
+++ trunk/src/main/org/jboss/seam/persistence/ManagedEntityWrapper.java	2008-11-17 05:15:05 UTC (rev 9578)
@@ -6,14 +6,16 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
-import java.util.List;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 
 import org.jboss.seam.Component;
 import org.jboss.seam.Seam;
 import org.jboss.seam.annotations.In;
 import org.jboss.seam.contexts.Contexts;
+import org.jboss.seam.core.Manager;
 import org.jboss.seam.log.LogProvider;
 import org.jboss.seam.log.Logging;
 import org.jboss.seam.util.Reflections;
@@ -22,7 +24,7 @@
  * @author Gavin King
  * @author Pete Muir
  * @author Norman Richards
- *
+ * @author Dan Allen
  */
 public class ManagedEntityWrapper
 {
@@ -31,98 +33,129 @@
    
    public void wrap(Object target, Component component) throws Exception
    {
-      if ( touchedContextsExist() )
+      if ( !touchedContextsExist() )
       {
-         Class beanClass = target.getClass();
-         for (; beanClass!=Object.class; beanClass=beanClass.getSuperclass())
+         log.trace("No touched persistence contexts. Therefore, there are no entities in this conversation whose identities need to be preserved.");
+         return;
+      }
+      
+      String oldCid = switchToConversationContextOfComponent(component);
+      Class beanClass = target.getClass();
+      for (; beanClass!=Object.class; beanClass=beanClass.getSuperclass())
+      {
+         log.trace("Examining fields on " + beanClass);
+         for ( Field field: beanClass.getDeclaredFields() )
          {
-            log.trace("Examining fields on " + beanClass);
-            for ( Field field: beanClass.getDeclaredFields() )
+            if ( !ignore(field) )
             {
-               if ( !ignore(field) )
+               Object value = getFieldValue(target, field);
+               if (value!=null)
                {
-                  Object value = getFieldValue(target, field);
-                  if (value!=null)
+                  Object dataModel = null;
+                  if ( DATA_MODEL.isInstance(value) )
                   {
-                     Object dataModel = null;
-                     if ( DATA_MODEL.isInstance(value) )
-                     {
-                        dataModel = value;
-                        value = getWrappedData(dataModel);
-                     }
-                     if ( isRef(value) )
-                     {
-                        log.trace("Attempting to save wrapper for " + field + " (" + value + ")");
-                        saveWrapper(target, component, field, dataModel, value);
-                     }
-                     else
-                     {
-                        log.trace("Clearing wrapper for " + field + " (" + value + ") as it isn't a entity reference");
-                        clearWrapper(component, field);
-                     }
+                     dataModel = value;
+                     value = getWrappedData(dataModel);
                   }
+                  if ( containsReferenceToEntityInstance(value) )
+                  {
+                     log.trace("Attempting to save wrapper for " + field + " (" + value + ")");
+                     saveWrapper(target, component, field, dataModel, value);
+                  }
                   else
                   {
-                     log.trace("Clearing wrapper for " + field + " as it is null");
+                     log.trace("Clearing wrapper for " + field + " (" + value + ") as it isn't a entity reference");
                      clearWrapper(component, field);
                   }
                }
                else
                {
-                  log.trace("Ignoring field " + field + " as it is static, transient or annotated with @In");
+                  log.trace("Clearing wrapper for " + field + " as it is null");
+                  clearWrapper(component, field);
                }
             }
+            else
+            {
+               log.trace("Ignoring field " + field + " as it is static, transient or annotated with @In");
+            }
          }
       }
-      else
-      {
-         log.trace("No touched persistence contexts");
-      }
+      restorePreviousConversationContextIfNecessary(oldCid);
    }
 
    public void deserialize(Object controllerBean, Component component) throws Exception
-   {      
-      if ( touchedContextsExist() )
+   {
+      if ( !touchedContextsExist() )
       {
-         Class beanClass = controllerBean.getClass();
-         for (; beanClass!=Object.class; beanClass=beanClass.getSuperclass())
+         log.trace("No touched persistence contexts. Therefore, there are no entities in this conversation whose identities need to be restored.");
+         return;
+      }
+      
+      Class beanClass = controllerBean.getClass();
+      for (; beanClass!=Object.class; beanClass=beanClass.getSuperclass())
+      {
+         log.trace("Examining fields on " + beanClass);
+         for ( Field field: beanClass.getDeclaredFields() )
          {
-            log.trace("Examining fields on " + beanClass);
-            for ( Field field: beanClass.getDeclaredFields() )
+            if ( !ignore(field) )
             {
-               if ( !ignore(field) )
+               Object value = getFieldValue(controllerBean, field);
+               Object dataModel = null;
+               if (value!=null && DATA_MODEL.isInstance(value) )
                {
-                  Object value = getFieldValue(controllerBean, field);
-                  Object dataModel = null;
-                  if (value!=null && DATA_MODEL.isInstance(value) )
-                  {
-                     dataModel = value;
-                  }
-                  log.trace("Attempting to restore wrapper for " + field + " (" + value + ")");
-                  //TODO: be more selective
-                  getFromWrapper(controllerBean, component, field, dataModel);
+                  dataModel = value;
                }
-               else
-               {
-                  log.trace("Ignoring field " + field + " as it is static, transient or annotated with @In");
-               }
+               log.trace("Attempting to restore wrapper for " + field + " (" + value + ")");
+               //TODO: be more selective
+               getFromWrapper(controllerBean, component, field, dataModel);
             }
+            else
+            {
+               log.trace("Ignoring field " + field + " as it is static, transient or annotated with @In");
+            }
          }
       }
-      else
-      {
-         log.trace("No touched persistence contexts");
-      }
    }
 
-   private boolean isRef(Object value)
+   private boolean containsReferenceToEntityInstance(Object value)
    {
-      //TODO: could do better by checking if the
-      //      collection really contains an entity
-      return value instanceof List || 
-            value instanceof Map || 
-            value instanceof Set || 
-            Seam.getEntityClass(value.getClass()) != null;
+      if (value == null)
+      {
+         return false;
+      }
+      else if (value instanceof Collection)
+      {
+         // Do a lazy man's generic check by scanning the collection until an entity is found (nested objects not considered).
+         for (Iterator iter = ((Collection) value).iterator(); iter.hasNext();)
+         {
+            Object v = iter.next();
+            if (v != null && Seam.getEntityClass(v.getClass()) != null)
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+      else if (value instanceof Map)
+      {
+         // Do a lazy man's generic check by scanning the collection until an entity is found (nested objects not considered).
+         for (Iterator iter = ((Map) value).entrySet().iterator(); iter.hasNext();)
+         {
+            Entry e = (Entry) iter.next();
+            if ((e.getKey() != null && Seam.getEntityClass(e.getKey().getClass()) != null) ||
+                  (e.getValue() != null && Seam.getEntityClass(e.getValue().getClass()) != null))
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+      else if (Seam.getEntityClass(value.getClass()) != null)
+      {
+         return true;
+      }
+      
+      return false;
    }
 
    private Object getFieldValue(Object bean, Field field) throws Exception
@@ -183,6 +216,37 @@
          }
       }
    }
-
    
+   /**
+    * Changes the thread's current conversation context to the one that holds a reference to this
+    * component. This is necessary if a nested conversation is making a call to a component in
+    * a parent conversation.
+    */
+   private String switchToConversationContextOfComponent(Component component)
+   {
+      Manager manager = Manager.instance();
+      if (manager.isNestedConversation())
+      {
+         String currentCid = manager.getCurrentConversationId();
+         String residentCid = manager.getCurrentConversationEntry().findPositionInConversationStack(component);
+         if (residentCid != currentCid)
+         {
+            Contexts.getConversationContext().flush();
+            Manager.instance().switchConversation(residentCid);
+            return currentCid;
+         }
+      }
+      
+      return null;
+   }
+   
+   private void restorePreviousConversationContextIfNecessary(String oldCid)
+   {
+      if (oldCid != null)
+      {
+         Contexts.getConversationContext().flush();
+         Manager.instance().switchConversation(oldCid);
+      }
+   }
+   
 }

Modified: trunk/src/test/integration/src/org/jboss/seam/test/integration/EntityPassivationTest.java
===================================================================
--- trunk/src/test/integration/src/org/jboss/seam/test/integration/EntityPassivationTest.java	2008-11-16 10:11:21 UTC (rev 9577)
+++ trunk/src/test/integration/src/org/jboss/seam/test/integration/EntityPassivationTest.java	2008-11-17 05:15:05 UTC (rev 9578)
@@ -1,6 +1,7 @@
 package org.jboss.seam.test.integration;
 
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -9,135 +10,243 @@
 
 import javax.persistence.EntityManager;
 
+import org.jboss.seam.Component;
 import org.jboss.seam.ScopeType;
 import org.jboss.seam.annotations.AutoCreate;
+import org.jboss.seam.annotations.Begin;
 import org.jboss.seam.annotations.In;
 import org.jboss.seam.annotations.Name;
 import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.contexts.Contexts;
 import org.jboss.seam.core.Conversation;
 import org.jboss.seam.mock.SeamTest;
+import org.jboss.seam.persistence.ManagedEntityInterceptor;
 import org.testng.annotations.Test;
 
-public class EntityPassivationTest
-    extends SeamTest
+/**
+ * Verifies the work of the ManagedEntityInterceptor. Specifically that
+ * collections containing entity instances are properly put into the session and
+ * pulled back into the conversation-scoped component between requests (during a
+ * potential period of passivation of the SFSB). The test also verifies that
+ * when a component in a nested conversation calls a component in a parent
+ * conversation, that the passivated state is kept with the parent conversation.
+ * 
+ * @author Norman Richards
+ * @author Dan Allen
+ */
+public class EntityPassivationTest extends SeamTest
 {
+   @Test
+   public void testEntityList() throws Exception
+   {
+      String pid = new FacesRequest("/test.xhtml")
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            // MEI is not installed by default, so we need to enable it
+            Component.forName("entitytest.someComponent").addInterceptor(new ManagedEntityInterceptor());
+            Component.forName("entitytest.nestedComponent").addInterceptor(new ManagedEntityInterceptor());
+            
+            Conversation.instance().begin(true, false);
 
-    @Test 
-    public void testEntityList() 
-        throws Exception 
-    {        
-        String id = new FacesRequest("/test.xhtml") {
-            @Override
-            protected void invokeApplication()
-                throws Exception 
-            {
-                Conversation.instance().begin(true, false);
+            invokeAction("#{entitytest.someComponent.createSomeThings}");
+            invokeAction("#{entitytest.someComponent.loadThings}");
+         }
 
-                invokeAction("#{entitytest.someComponent.createSomeThings}");
-                invokeAction("#{entitytest.someComponent.loadThings}");
+         @Override
+         protected void renderResponse() throws Exception
+         {
+            Object thing = getValue("#{entitytest.someComponent.thing}");
+            assert thing != null;
 
-            }
-            @Override
-            protected void renderResponse()
-                throws Exception 
-            {
-                Object thing = getValue("#{entitytest.someComponent.thing}");
-                assert thing!=null;      
-                
-                List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
-                assert thingList!=null && !thingList.isEmpty();            
-                assert thingList.get(0) != null;       
-            
-                
-                Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
-                assert thingSet!=null && thingSet.size() > 0;                
-                assert thingSet.iterator().next() != null;    
-                
-                Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
-                assert thingMap!=null && thingMap.size() > 0;
-            }
-        }.run();
+            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
+            assert thingList != null && !thingList.isEmpty();
+            assert thingList.get(0) != null;
 
-        new FacesRequest("/test.xhtml", id) {
-            // the entities should be passivated 
-        }.run();
+            Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
+            assert thingSet != null && thingSet.size() > 0;
+            assert thingSet.iterator().next() != null;
 
-        new FacesRequest("/test.xhtml", id) {
-            // passivated a second time
-        }.run();
+            Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
+            assert thingMap != null && thingMap.size() > 0;
+         }
+      }.run();
 
-        new FacesRequest("/test.xhtml", id) {
-            @Override
-            protected void renderResponse()
-                throws Exception 
-            {
-                Object thing = getValue("#{entitytest.someComponent.thing}");
-                assert thing!=null;      
-                
-                List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
-                assert thingList!=null && !thingList.isEmpty();            
-                assert thingList.get(0) != null;       
-            
-                
-                Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
-                assert thingSet!=null && thingSet.size() > 0;                
-                assert thingSet.iterator().next() != null;
-                
-                Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
-                assert thingMap!=null && thingMap.size() > 0;
-            }
-            
-            
-        }.run();
-    }
-    
-    
-    
-    @Name("entitytest.someComponent")
-    @Scope(ScopeType.CONVERSATION)
-    @AutoCreate
-    public static class SomeComponent implements Serializable {
-        @In EntityManager entityManager;
-        
-        
-        Set<UnversionedThing> thingSet;
-        List<UnversionedThing> thingList;
-        Map<Long,UnversionedThing> thingMap;
-        UnversionedThing thing;
-        
-        public void loadThings() {
-            thingList = entityManager.createQuery("select t from UnversionedThing t").getResultList();
-            thingSet = new HashSet<UnversionedThing>(thingList);
-            
-            thingMap = new HashMap<Long,UnversionedThing>();
-            for (UnversionedThing thing: thingList) {
-                thingMap.put(thing.getId(),thing);
-            }
-            
-            thing = thingList.get(0);
-        }
-        
-        public List<UnversionedThing> getThingsAsList() {
-            return thingList;
-        }
-        
-        public Set<UnversionedThing> getThingsAsSet() {
-            return thingSet;
-        }
-        
-        public Map<Long,UnversionedThing> getThingsAsMap() {
-            return thingMap;
-        }
-        
-        public UnversionedThing getThing() {
-            return thing;
-        }
-        
-        public void createSomeThings() {
-            UnversionedThing thing1 = new UnversionedThing();
-            thing1.setName("thing one");
-            entityManager.persist(thing1);
-        }
-        
-    }
+      new FacesRequest("/test.xhtml", pid)
+      {
+         // the entities should be passivated
+      }.run();
+
+      new FacesRequest("/test.xhtml", pid)
+      {
+         // passivated a second time
+      }.run();
+
+      new FacesRequest("/test.xhtml", pid)
+      {
+         @Override
+         protected void renderResponse() throws Exception
+         {
+            Object thing = getValue("#{entitytest.someComponent.thing}");
+            assert thing != null;
+
+            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
+            assert thingList != null && !thingList.isEmpty();
+            assert thingList.get(0) != null;
+
+            Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
+            assert thingSet != null && thingSet.size() > 0;
+            assert thingSet.iterator().next() != null;
+
+            Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
+            assert thingMap != null && thingMap.size() > 0;
+         }
+
+      }.run();
+
+      // Start a nested conversation to verify that calls to a component in a parent conversation
+      // will passivate that component's state in the parent conversation context and not in
+      // the nested conversation. Thus, when the parent conversation is restored, its state
+      // will be properly restored.
+      String nid = new FacesRequest("/test.xhtml", pid)
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            invokeMethod("#{entitytest.nestedComponent.nest}");
+         }
+
+         @Override
+         protected void renderResponse() throws Exception
+         {
+            assert Conversation.instance().isNested();
+         }
+
+      }.run();
+      
+      new FacesRequest("/test.xhtml", nid)
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            // invoke component in parent conversation from nested conversation
+            invokeMethod("#{entitytest.someComponent.removeFirstThingFromList}");
+         }
+         
+         @Override
+         protected void renderResponse() throws Exception
+         {
+            // the nested conversation should not hold the serialized property of the component in the parent conversation
+            assert !Arrays.asList(Contexts.getConversationContext().getNames()).contains("entitytest.someComponent.thingList");
+            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
+            assert thingList.size() == 1;
+         }   
+      }.run();
+      
+      new FacesRequest("/test.xhtml", nid)
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            invokeMethod("#{entitytest.nestedComponent.end}");
+         }  
+      }.run();
+      
+      new FacesRequest("/test.xhtml", pid)
+      {
+         @Override
+         protected void renderResponse() throws Exception
+         {
+            // The state of the component in the parent conversation should be preserved.
+            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
+            assert thingList.size() == 1;
+         }  
+      }.run();
+   }
+
+   @Name("entitytest.someComponent")
+   @Scope(ScopeType.CONVERSATION)
+   @AutoCreate
+   public static class SomeComponent implements Serializable
+   {
+      @In
+      EntityManager entityManager;
+
+      Set<UnversionedThing> thingSet;
+      List<UnversionedThing> thingList;
+      Map<Long, UnversionedThing> thingMap;
+      UnversionedThing thing;
+
+      public void loadThings()
+      {
+         thingList = entityManager.createQuery("select t from UnversionedThing t").getResultList();
+         thingSet = new HashSet<UnversionedThing>(thingList);
+
+         thingMap = new HashMap<Long, UnversionedThing>();
+         for (UnversionedThing thing : thingList)
+         {
+            thingMap.put(thing.getId(), thing);
+         }
+
+         thing = thingList.get(0);
+      }
+
+      public List<UnversionedThing> getThingsAsList()
+      {
+         return thingList;
+      }
+
+      public Set<UnversionedThing> getThingsAsSet()
+      {
+         return thingSet;
+      }
+
+      public Map<Long, UnversionedThing> getThingsAsMap()
+      {
+         return thingMap;
+      }
+
+      public UnversionedThing getThing()
+      {
+         return thing;
+      }
+      
+      public void removeFirstThingFromList()
+      {
+         loadThings();
+         thingList.remove(0);
+      }
+
+      public void createSomeThings()
+      {
+         UnversionedThing thing1 = new UnversionedThing();
+         thing1.setName("thing one");
+         entityManager.persist(thing1);
+         
+         UnversionedThing thing2 = new UnversionedThing();
+         thing1.setName("thing two");
+         entityManager.persist(thing2);
+      }
+
+   }
+
+   @Name("entitytest.nestedComponent")
+   @Scope(ScopeType.CONVERSATION)
+   @AutoCreate
+   public static class NestedComponent implements Serializable
+   {
+      @In
+      EntityManager entityManager;
+
+      @Begin(nested = true)
+      public void nest()
+      {
+      }
+
+      public void end()
+      {
+         Conversation.instance().end();
+      }
+   }
 }




More information about the seam-commits mailing list