Index: extensions/trunk/se/src/main/java/org/jboss/webbeans/environment/se/lifecycle/JavaSELifecycle.java
===================================================================
--- extensions/trunk/se/src/main/java/org/jboss/webbeans/environment/se/lifecycle/JavaSELifecycle.java	(revision 0)
+++ extensions/trunk/se/src/main/java/org/jboss/webbeans/environment/se/lifecycle/JavaSELifecycle.java	(revision 0)
@@ -0,0 +1,66 @@
+package org.jboss.webbeans.environment.se.lifecycle;
+
+import javax.context.ApplicationScoped;
+import org.jboss.webbeans.CurrentManager;
+import org.jboss.webbeans.context.ConversationContext;
+import org.jboss.webbeans.context.DependentContext;
+import org.jboss.webbeans.context.RequestContext;
+import org.jboss.webbeans.context.SessionContext;
+import org.jboss.webbeans.context.api.BeanStore;
+import org.jboss.webbeans.context.api.helpers.ConcurrentHashMapBeanStore;
+import org.jboss.webbeans.conversation.ConversationManager;
+import org.jboss.webbeans.log.LogProvider;
+import org.jboss.webbeans.log.Logging;
+
+/**
+ * An implementation of the Web Beans lifecycle that supports restoring
+ * and destroying some of the built in contexts in an SE environment.
+ * Based on the AbstractLifecycle found in WebBeans Core.
+ * TODO(PR): this should possibly be pushed to the SPI as an interface. Work
+ * with Pete M on this.
+ * 
+ * @author Peter Royle
+ * 
+ */
+@ApplicationScoped
+public class JavaSELifecycle
+{
+
+   private static LogProvider log = Logging.getLogProvider(JavaSELifecycle.class);
+
+   public void beginRequest(String id, BeanStore requestBeanStore)
+   {
+      log.trace("Starting request " + id);
+      RequestContext.instance().setBeanStore(requestBeanStore);
+      RequestContext.instance().setActive(true);
+      DependentContext.instance().setActive(true);
+   }
+
+   public void endRequest(String id, BeanStore requestBeanStore)
+   {
+      log.trace("Ending request " + id);
+      RequestContext.instance().setBeanStore(requestBeanStore);
+      DependentContext.instance().setActive(false);
+      RequestContext.instance().destroy();
+      RequestContext.instance().setActive(false);
+   }
+
+   public void startSession()
+   {
+      ConversationContext.instance().setBeanStore(new ConcurrentHashMapBeanStore());
+      ConversationContext.instance().setActive(true);
+      SessionContext.instance().setBeanStore(new ConcurrentHashMapBeanStore());
+      SessionContext.instance().setActive(true);
+   }
+
+   protected void endSession(String id, BeanStore sessionBeanStore)
+   {
+      log.trace("Ending session " + id);
+      SessionContext.instance().setActive(true);
+      ConversationManager conversationManager = CurrentManager.rootManager().getInstanceByType(ConversationManager.class);
+      conversationManager.destroyAllConversations();
+      SessionContext.instance().destroy();
+      SessionContext.instance().setBeanStore(null);
+      SessionContext.instance().setActive(false);
+   }
+}
Index: ri/trunk/impl/src/main/java/org/jboss/webbeans/conversation/JavaSEConversationManager.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/conversation/JavaSEConversationManager.java	(revision 0)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/conversation/JavaSEConversationManager.java	(revision 0)
@@ -0,0 +1,280 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jboss.webbeans.conversation;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+
+import javax.context.Conversation;
+import javax.context.SessionScoped;
+import javax.inject.Current;
+import javax.inject.Produces;
+import javax.servlet.http.HttpSession;
+
+import org.jboss.webbeans.WebBean;
+import org.jboss.webbeans.context.ConversationContext;
+import org.jboss.webbeans.log.LogProvider;
+import org.jboss.webbeans.log.Logging;
+
+/**
+ * Conversation manager for the SE environment.
+ *
+ * TODO(PR): Pull this and ServletConversationManager into an abstract superclass?
+ *
+ * @author Nicklas Karlsson
+ * @author Peter Royle
+ * @see javax.context.Conversation
+ */
+@SessionScoped
+@WebBean
+public class JavaSEConversationManager implements ConversationManager, Serializable {
+
+   private static LogProvider log = Logging.getLogProvider(JavaSEConversationManager.class);
+
+   // TODO (PR): what do timeouts mean in SE?
+   private static final long CONVERSATION_TIMEOUT_IN_MS = 10 * 60 * 1000;
+   private static final long CONVERSATION_CONCURRENT_ACCESS_TIMEOUT_IN_MS = 1 * 1000;
+   private static final String CONVERSATION_ID_NAME = "cid";
+
+   // The conversation terminator
+   @Current
+   private ConversationTerminator conversationTerminator;
+
+   // The current conversation
+   @Current
+   private ConversationImpl currentConversation;
+
+   // The conversation timeout in milliseconds waiting for access to a blocked
+   // conversation
+   @ConversationConcurrentAccessTimeout
+   private long concurrentAccessTimeout;
+
+   // A map of current active long-running conversation entries
+   private Map<String, ConversationEntry> longRunningConversations;
+
+   /**
+    * Creates a new conversation manager
+    */
+   public JavaSEConversationManager()
+   {
+      log.trace("Created " + getClass());
+      longRunningConversations = new ConcurrentHashMap<String, ConversationEntry>();
+   }
+
+   @Produces
+   @ConversationInactivityTimeout
+   @WebBean
+   public static long getConversationTimeoutInMilliseconds()
+   {
+      log.trace("Produced conversation timeout " + CONVERSATION_TIMEOUT_IN_MS);
+      return CONVERSATION_TIMEOUT_IN_MS;
+   }
+
+   @Produces
+   @ConversationConcurrentAccessTimeout
+   @WebBean
+   public static long getConversationConcurrentAccessTimeout()
+   {
+      log.trace("Produced conversation concurrent access timeout " + CONVERSATION_CONCURRENT_ACCESS_TIMEOUT_IN_MS);
+      return CONVERSATION_CONCURRENT_ACCESS_TIMEOUT_IN_MS;
+   }
+
+   @Produces
+   @ConversationIdName
+   @WebBean
+   public static String getConversationIdName()
+   {
+      log.trace("Produced conversation id name " + CONVERSATION_ID_NAME);
+      return CONVERSATION_ID_NAME;
+   }
+
+   public void beginOrRestoreConversation(String cid)
+   {
+      if (cid == null)
+      {
+         // No incoming conversation ID, nothing to do here, continue with
+         // a transient conversation
+         log.trace("No conversation id to restore");
+         return;
+      }
+      if (!longRunningConversations.containsKey(cid))
+      {
+         // We got an incoming conversation ID but it was not in the map of
+         // known ones, nothing to do. Log and return to continue with a
+         // transient conversation
+         log.warn("Could not restore long-running conversation " + cid);
+         return;
+      }
+      ConversationEntry resumedConversationEntry = longRunningConversations.get(cid);
+      // Try to get a lock to the requested conversation, log and return to
+      // continue with a transient conversation if we fail
+      try
+      {
+         if (!resumedConversationEntry.lock(concurrentAccessTimeout))
+         {
+            return;
+         }
+      }
+      catch (InterruptedException e)
+      {
+         log.debug("Interrupted while trying to acquire lock");
+         return;
+      }
+      // If we can't cancel the termination, release the lock, return and
+      // continue
+      // with a transient conversation
+      if (!resumedConversationEntry.cancelTermination())
+      {
+         resumedConversationEntry.unlock();
+      }
+      else
+      {
+         // If all goes well, set the identity of the current conversation to
+         // match the fetched long-running one
+         String oldConversation = currentConversation.toString();
+         currentConversation.switchTo(resumedConversationEntry.getConversation());
+         log.trace("Conversation switched from " + oldConversation + " to " + currentConversation);
+      }
+   }
+
+   // TODO: check that stuff gets terminated when you flip between several
+   // long-running conversations
+   public void cleanupConversation()
+   {
+      log.trace("Cleaning up conversation for " + currentConversation);
+      String cid = currentConversation.getId();
+      if (currentConversation.isLongRunning())
+      {
+         Future<?> terminationHandle = scheduleForTermination(cid, currentConversation.getTimeout());
+         // When the conversation ends, a long-running conversation needs to
+         // start its self-destruct. We can have the case where the conversation
+         // is a previously known conversation (that had it's termination
+         // canceled in the
+         // beginConversation) or the case where we have a completely new
+         // long-running conversation.
+         ConversationEntry longRunningConversation = longRunningConversations.get(cid);
+         if (longRunningConversation != null)
+         {
+            longRunningConversation.unlock();
+            longRunningConversation.reScheduleTermination(terminationHandle);
+         }
+         else
+         {
+            ConversationEntry conversationEntry = ConversationEntry.of(currentConversation, terminationHandle);
+            longRunningConversations.put(cid, conversationEntry);
+         }
+         log.trace("Scheduled " + currentConversation + " for termination, there are now " + longRunningConversations.size() + " long-running conversations");
+      }
+      else
+      {
+         // If the conversation is not long-running it can be a transient
+         // conversation that has been so from the start or it can be a
+         // long-running conversation that has been demoted during the request
+         log.trace("Destroying transient conversation " + currentConversation);
+         ConversationEntry longRunningConversation = longRunningConversations.remove(cid);
+         if (longRunningConversation != null)
+         {
+            longRunningConversation.cancelTermination();
+            longRunningConversation.unlock();
+         }
+         ConversationContext.instance().destroy();
+      }
+      // If the conversation has been switched from one long
+      // running-conversation to another with
+      // Conversation.begin(String), we need to unlock the original conversation
+      // and re-schedule
+      // it for termination
+      String originalCid = currentConversation.getOriginalCid();
+      ConversationEntry longRunningConversation = originalCid == null ? null : longRunningConversations.get(originalCid);
+      if (longRunningConversation != null)
+      {
+         longRunningConversation.unlock();
+         longRunningConversation.reScheduleTermination(scheduleForTermination(originalCid, currentConversation.getTimeout()));
+      }
+   }
+
+   /**
+    * Creates a termination task for and schedules it
+    * 
+    * @param cid The id of the conversation to terminate
+    * @return The asynchronous job handle
+    */
+   private Future<?> scheduleForTermination(String cid, long timeout)
+   {
+      Runnable terminationTask = new TerminationTask(cid);
+      return conversationTerminator.scheduleForTermination(terminationTask, timeout);
+   }
+
+   /**
+    * A termination task that destroys the conversation entry
+    * 
+    * @author Nicklas Karlsson
+    * 
+    */
+   private class TerminationTask implements Runnable
+   {
+      // The conversation ID to terminate
+      private String cid;
+      private HttpSession session;
+
+      /**
+       * Creates a new termination task
+       * 
+       * @param cid The conversation ID
+       */
+      public TerminationTask(String cid)
+      {
+         this.cid = cid;
+      }
+
+      /**
+       * Executes the termination
+       */
+      public void run()
+      {
+         log.debug("Conversation " + cid + " timed out. Destroying it");
+         longRunningConversations.remove(cid).destroy(session);
+         log.trace("There are now " + longRunningConversations.size() + " long-running conversations");
+      }
+   }
+
+   public void destroyAllConversations()
+   {
+      log.debug("Destroying " + longRunningConversations.size() + " long-running conversations");
+      for (ConversationEntry conversationEntry : longRunningConversations.values())
+      {
+         conversationEntry.destroy(null); // TODO(PR): the null here represents a session. Need to work this out.
+      }
+      longRunningConversations.clear();
+   }
+
+   public Set<Conversation> getLongRunningConversations()
+   {
+      Set<Conversation> conversations = new HashSet<Conversation>();
+      for (ConversationEntry conversationEntry : longRunningConversations.values())
+      {
+         conversations.add(conversationEntry.getConversation());
+      }
+      return Collections.unmodifiableSet(conversations);
+   }
+
+}
Index: ri/trunk/impl/src/main/java/org/jboss/webbeans/bootstrap/WebBeansBootstrap.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/bootstrap/WebBeansBootstrap.java	(revision 2638)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/bootstrap/WebBeansBootstrap.java	(working copy)
@@ -42,6 +42,7 @@
 import org.jboss.webbeans.context.api.BeanStore;
 import org.jboss.webbeans.context.api.helpers.ConcurrentHashMapBeanStore;
 import org.jboss.webbeans.conversation.ConversationImpl;
+import org.jboss.webbeans.conversation.JavaSEConversationManager;
 import org.jboss.webbeans.conversation.JavaSEConversationTerminator;
 import org.jboss.webbeans.conversation.NumericConversationIdGenerator;
 import org.jboss.webbeans.conversation.ServletConversationManager;
@@ -154,12 +155,15 @@
       beanDeployer.addBean(InjectionPointBean.of(manager));
       beanDeployer.addBean(EventBean.of(manager));
       beanDeployer.addBean(InstanceBean.of(manager));
+
+      beanDeployer.addClass(ConversationImpl.class);
+      beanDeployer.addClass(JavaSEConversationManager.class);
+      beanDeployer.addClass(JavaSEConversationTerminator.class);
+      beanDeployer.addClass(NumericConversationIdGenerator.class);
+
       if (!getEnvironment().equals(Environments.SE))
       {
-         beanDeployer.addClass(ConversationImpl.class);
          beanDeployer.addClass(ServletConversationManager.class);
-         beanDeployer.addClass(JavaSEConversationTerminator.class);
-         beanDeployer.addClass(NumericConversationIdGenerator.class);
          beanDeployer.addClass(HttpSessionManager.class);
       }
       beanDeployer.addBeans(resourceBeans);

Property changes on: examples
___________________________________________________________________
Added: svn:ignore
   + .numbergess-contexts.diff.swp


Index: examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/Game.java
===================================================================
--- examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/Game.java	(revision 2638)
+++ examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/Game.java	(working copy)
@@ -19,24 +19,24 @@
 import java.io.Serializable;
 
 import javax.annotation.PostConstruct;
-import javax.context.ApplicationScoped;
-import javax.inject.Current;
+import javax.context.ConversationScoped;
 
 /**
- * This class contains the pure logic of the Number Guess game.
- * On initialization a random number will be selected. Multiple "guesses"
+ * This class contains the pure logic of a single instance of the Number Guess
+ * game. On initialization a random number will be selected. Multiple "guesses"
  * as to what that number might be can be made using the 'check' method. The
  * user wins if they can guess the selected number in the alloted amount of
  * tries.
- * 
+ *
  * @author Peter Royle
  */
-@ApplicationScoped
+@ConversationScoped
 public class Game
     implements Serializable
 {
+
     public static final int MAX_NUM_GUESSES = 10;
-    
+    @Random
     private Integer number;
     private int guess = 0;
     private int smallest = 0;
@@ -45,8 +45,6 @@
     private int remainingGuesses = MAX_NUM_GUESSES;
     private boolean validNumberRange = true;
 
-    @Current Generator rndGenerator;
-
     public Game()
     {
     }
@@ -131,11 +129,7 @@
     @PostConstruct
     public void reset()
     {
-        this.smallest = 0;
-        this.guess = 0;
-        this.remainingGuesses = 10;
         this.biggest = maxNumber;
-        this.number = rndGenerator.next();
-        System.out.println( "psst! the number is " + this.number );
+        System.out.println("psst! the number is " + this.number);
     }
 }
Index: examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/NumberGuessFrame.java
===================================================================
--- examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/NumberGuessFrame.java	(revision 2638)
+++ examples/trunk/se/numberguess/src/main/java/org/jboss/webbeans/environment/se/example/numberguess/NumberGuessFrame.java	(working copy)
@@ -17,21 +17,28 @@
 package org.jboss.webbeans.environment.se.example.numberguess;
 
 import java.awt.CardLayout;
-
+import javax.context.Conversation;
 import javax.event.Observes;
 import javax.inject.Current;
 import javax.inject.manager.Deployed;
 import javax.inject.manager.Manager;
+import org.jboss.webbeans.context.api.helpers.ConcurrentHashMapBeanStore;
+import org.jboss.webbeans.conversation.ConversationManager;
+import org.jboss.webbeans.environment.se.lifecycle.JavaSELifecycle;
 
 /**
  * Swing-based number guess example, main application frame.
  * @author Peter Royle
  */
 public class NumberGuessFrame
-    extends javax.swing.JFrame
+        extends javax.swing.JFrame
 {
     private @Current Game game;
     private @Current MessageGenerator msgGenerator;
+    private @Current Conversation conversation;
+    private @Current ConversationManager conMan;
+    // TODO(PR): if this changes to an interface, make the change here as well
+    private @Current JavaSELifecycle lifecycle;
 
     /** Creates new form NumberGuessFrame */
     public NumberGuessFrame()
@@ -41,13 +48,19 @@
     public void start( @Observes @Deployed Manager manager )
     {
         java.awt.EventQueue.invokeLater( new Runnable()
+        {
+            public void run()
             {
-                public void run()
-                {
-                    initComponents();
-                    setVisible( true );
-                }
-            } );
+                lifecycle.startSession();
+                final ConcurrentHashMapBeanStore reqBeanStore = new ConcurrentHashMapBeanStore();
+                lifecycle.beginRequest(null, reqBeanStore);
+
+                conversation.begin();
+                initComponents();
+                setVisible( true );
+                lifecycle.endRequest(null, reqBeanStore);
+            }
+        });
     }
 
     /** This method is called to initialize the form.
@@ -135,6 +148,9 @@
 
     private void guessButtonActionPerformed( java.awt.event.ActionEvent evt )
     {
+        final ConcurrentHashMapBeanStore reqBeanStore = new ConcurrentHashMapBeanStore();
+        lifecycle.beginRequest(null, reqBeanStore);
+
         int guess = -1;
 
         try
@@ -153,13 +169,20 @@
         {
             switchButtons();
         }
+        lifecycle.endRequest(null, reqBeanStore);
     }
 
     private void replayBtnActionPerformed( java.awt.event.ActionEvent evt )
     {
-       game.reset();
-       refreshUI();
-       switchButtons();
+        final ConcurrentHashMapBeanStore reqBeanStore = new ConcurrentHashMapBeanStore();
+        lifecycle.beginRequest(null, reqBeanStore);
+
+        conversation.end();
+        conMan.cleanupConversation();
+
+        refreshUI();
+        switchButtons();
+        lifecycle.endRequest(null, reqBeanStore);
     }
 
     private void switchButtons()
Index: examples/trunk/se/numberguess/pom.xml
===================================================================
--- examples/trunk/se/numberguess/pom.xml	(revision 2638)
+++ examples/trunk/se/numberguess/pom.xml	(working copy)
@@ -16,6 +16,7 @@
         <dependency>
             <groupId>org.jboss.webbeans</groupId>
             <artifactId>webbeans-se</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
         </dependency>
     </dependencies>
     
