[jboss-cvs] JBoss Messaging SVN: r1880 - in trunk: . src/etc src/main/org/jboss/jms src/main/org/jboss/jms/client src/main/org/jboss/jms/client/container src/main/org/jboss/jms/client/delegate src/main/org/jboss/jms/client/remoting src/main/org/jboss/jms/client/state src/main/org/jboss/jms/delegate src/main/org/jboss/jms/server src/main/org/jboss/jms/server/connectionfactory src/main/org/jboss/jms/server/endpoint src/main/org/jboss/jms/server/endpoint/advised src/main/org/jboss/jms/server/remoting src/main/org/jboss/jms/tx src/main/org/jboss/messaging/core/tx tests tests/src/org/jboss/test/messaging/jms tests/src/org/jboss/test/messaging/jms/clustering tests/src/org/jboss/test/messaging/jms/clustering/base tests/src/org/jboss/test/messaging/tools tests/src/org/jboss/test/messaging/tools/jmx

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Sun Dec 31 05:52:07 EST 2006


Author: ovidiu.feodorov at jboss.com
Date: 2006-12-31 05:51:38 -0500 (Sun, 31 Dec 2006)
New Revision: 1880

Added:
   trunk/src/main/org/jboss/jms/client/ClientAOPStackLoader.java
   trunk/src/main/org/jboss/jms/client/ClientAOPStackProvider.java
   trunk/src/main/org/jboss/jms/client/FailoverEvent.java
   trunk/src/main/org/jboss/jms/client/FailoverListener.java
   trunk/src/main/org/jboss/jms/client/Valve.java
   trunk/src/main/org/jboss/jms/client/container/ClusteringAspect.java
   trunk/src/main/org/jboss/jms/client/container/ConnectionFailureListener.java
   trunk/src/main/org/jboss/jms/client/container/FailoverAspect.java
   trunk/src/main/org/jboss/jms/client/container/FailoverValveInterceptor.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientClusteredConnectionFactoryDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/Initializable.java
   trunk/src/main/org/jboss/jms/server/endpoint/advised/ClientAOPStackProviderAdvised.java
   trunk/tests/src/org/jboss/test/messaging/jms/AOPStackInitializationTest.java
   trunk/tests/src/org/jboss/test/messaging/jms/FailoverValveTest.java
   trunk/tests/src/org/jboss/test/messaging/jms/clustering/LoadBalancingTest.java
Removed:
   trunk/src/main/org/jboss/jms/client/container/HAAspect.java
   trunk/src/main/org/jboss/jms/client/container/ValveAspect.java
   trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java
   trunk/src/main/org/jboss/jms/perf/
Modified:
   trunk/messaging.ipr
   trunk/src/etc/aop-messaging-client.xml
   trunk/src/etc/prepare-aop.xml
   trunk/src/main/org/jboss/jms/client/JBossConnection.java
   trunk/src/main/org/jboss/jms/client/JBossConnectionFactory.java
   trunk/src/main/org/jboss/jms/client/JBossMessageProducer.java
   trunk/src/main/org/jboss/jms/client/JBossQueueBrowser.java
   trunk/src/main/org/jboss/jms/client/container/ClosedInterceptor.java
   trunk/src/main/org/jboss/jms/client/container/ConnectionAspect.java
   trunk/src/main/org/jboss/jms/client/container/ConsumerAspect.java
   trunk/src/main/org/jboss/jms/client/container/ExceptionInterceptor.java
   trunk/src/main/org/jboss/jms/client/container/SessionAspect.java
   trunk/src/main/org/jboss/jms/client/container/StateCreationAspect.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientBrowserDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionFactoryDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientConsumerDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientProducerDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/ClientSessionDelegate.java
   trunk/src/main/org/jboss/jms/client/delegate/DelegateSupport.java
   trunk/src/main/org/jboss/jms/client/remoting/CallbackManager.java
   trunk/src/main/org/jboss/jms/client/remoting/ConsolidatedRemotingConnectionListener.java
   trunk/src/main/org/jboss/jms/client/state/BrowserState.java
   trunk/src/main/org/jboss/jms/client/state/ConnectionState.java
   trunk/src/main/org/jboss/jms/client/state/ConsumerState.java
   trunk/src/main/org/jboss/jms/client/state/HierarchicalState.java
   trunk/src/main/org/jboss/jms/client/state/HierarchicalStateSupport.java
   trunk/src/main/org/jboss/jms/client/state/ProducerState.java
   trunk/src/main/org/jboss/jms/client/state/SessionState.java
   trunk/src/main/org/jboss/jms/delegate/BrowserDelegate.java
   trunk/src/main/org/jboss/jms/delegate/ConnectionDelegate.java
   trunk/src/main/org/jboss/jms/delegate/ConnectionFactoryDelegate.java
   trunk/src/main/org/jboss/jms/delegate/ConsumerDelegate.java
   trunk/src/main/org/jboss/jms/delegate/ProducerDelegate.java
   trunk/src/main/org/jboss/jms/delegate/SessionDelegate.java
   trunk/src/main/org/jboss/jms/server/ConnectionFactoryManager.java
   trunk/src/main/org/jboss/jms/server/ServerPeer.java
   trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactory.java
   trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactoryJNDIMapper.java
   trunk/src/main/org/jboss/jms/server/endpoint/ConnectionFactoryEndpoint.java
   trunk/src/main/org/jboss/jms/server/endpoint/DeliveryInfo.java
   trunk/src/main/org/jboss/jms/server/endpoint/ServerConnectionFactoryEndpoint.java
   trunk/src/main/org/jboss/jms/server/endpoint/ServerConsumerEndpoint.java
   trunk/src/main/org/jboss/jms/server/endpoint/advised/ConnectionFactoryAdvised.java
   trunk/src/main/org/jboss/jms/server/remoting/JMSDispatcher.java
   trunk/src/main/org/jboss/jms/tx/ResourceManagerFactory.java
   trunk/src/main/org/jboss/messaging/core/tx/TransactionRepository.java
   trunk/tests/build.xml
   trunk/tests/src/org/jboss/test/messaging/jms/clustering/FailoverTest.java
   trunk/tests/src/org/jboss/test/messaging/jms/clustering/HAStressTest.java
   trunk/tests/src/org/jboss/test/messaging/jms/clustering/HATest.java
   trunk/tests/src/org/jboss/test/messaging/jms/clustering/base/ClusteringTestBase.java
   trunk/tests/src/org/jboss/test/messaging/tools/ServerManagement.java
   trunk/tests/src/org/jboss/test/messaging/tools/jmx/ServiceContainer.java
Log:
1. The clustering/failover support has been refactored as follows:

There is a new PER_INSTANCE ClusteringAspect  part of ClusteredConnectionFactory's stack, 
which encapsulates load balancing and failover node picking policies. Each client-side 
delegate is guarded by a PER_INSTANCE FailoverValveInterceptor instance. The valve is 
normally open. On failover, all valves under a connection are closed recursively. If there 
are active threads through valve on closing, the current behavior is that closeValve() 
waits until all active threads unwrap (I could change this if necessary). Once the valve 
is closed, no threads are allowed through it until is explicitly opened again. 
The client-side failover event (which includes operating the valves, among other things) 
is detected and controlled by PER_VM FailoverAspect.

2. ClientClusteredConnectionFactoryDelegate doesn't extend ClientConnectionFactoryDelegate
 anymore. Also, I introduced two distinct aspect stacks corresponding to clustered 
ConnectionFactories and non-clustered ConnectionFactories.

3. One can register a FailoverListener to a JBossConnection and so to be notified when 
a failover starts and ends. So far, known events are FAILURE_DETECTED, FAILOVER_STARTED, 
FAILOVER_COMPLETED, FAILOVER_FAILED.

4. The initialization of the AOP stack is done by a specialized component 
(ClientAOPStackLoader). Both ConnectionDelegate and ClusteredConnectionDelegate delegate 
to it. getAOPStackConfig() is not part of the ConnectionFactory interface anymore.


Modified: trunk/messaging.ipr
===================================================================
--- trunk/messaging.ipr	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/messaging.ipr	2006-12-31 10:51:38 UTC (rev 1880)
@@ -234,7 +234,9 @@
         <root url="jar://$PROJECT_DIR$/thirdparty/jboss/aop/lib/jboss-aop.jar!/" />
       </CLASSES>
       <JAVADOC />
-      <SOURCES />
+      <SOURCES>
+        <root url="file://C:/work/src/jboss-aop_1.5.0.GA/src/aspect-framework/main" />
+      </SOURCES>
     </library>
     <library name="jboss-remoting">
       <CLASSES>

Modified: trunk/src/etc/aop-messaging-client.xml
===================================================================
--- trunk/src/etc/aop-messaging-client.xml	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/etc/aop-messaging-client.xml	2006-12-31 10:51:38 UTC (rev 1880)
@@ -8,6 +8,7 @@
    <interceptor class="org.jboss.jms.client.container.ClientLogInterceptor" scope="PER_VM"/>
    <interceptor class="org.jboss.jms.client.container.ClosedInterceptor" scope="PER_INSTANCE"/>                  	
    <interceptor class="org.jboss.jms.client.container.ConcurrencyInterceptor" scope="PER_INSTANCE"/>
+   <interceptor class="org.jboss.jms.client.container.FailoverValveInterceptor" scope="PER_INSTANCE"/>
 
    <aspect class="org.jboss.jms.client.container.StateCreationAspect" scope="PER_VM"/>
    <aspect class="org.jboss.jms.client.container.ConsumerAspect" scope="PER_VM"/>
@@ -16,24 +17,45 @@
    <aspect class="org.jboss.jms.client.container.AsfAspect" scope="PER_INSTANCE"/>
    <aspect class="org.jboss.jms.client.container.BrowserAspect" scope="PER_INSTANCE"/>
    <aspect class="org.jboss.jms.client.container.ConnectionAspect" scope="PER_INSTANCE"/>
-   <aspect class="org.jboss.jms.client.container.HAAspect" scope="PER_INSTANCE"/>
+   <aspect class="org.jboss.jms.client.container.ClusteringAspect" scope="PER_INSTANCE"/>
    <aspect class="org.jboss.jms.client.container.FactoryAspect" scope="PER_INSTANCE"/>
+   <aspect class="org.jboss.jms.client.container.FailoverAspect" scope="PER_VM"/>
 
-   <!-- ConnectionFactory Stack-->        
+   <!--
+       Clustered ConnectionFactory Stack
+   -->
+
+   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate->$implementing{org.jboss.jms.delegate.ConnectionFactoryDelegate}(..))">
+      <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>
+   </bind>
+   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate->createConnectionDelegate(..))">
+      <advice name="handleCreateConnectionDelegate" aspect="org.jboss.jms.client.container.ClusteringAspect"/>
+   </bind>
+
+   <!--
+        (Non-clustered) ConnectionFactory Stack
+   -->
+
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate->$implementing{org.jboss.jms.delegate.ConnectionFactoryDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>
    </bind>   
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate->createConnectionDelegate(..))">
       <advice name="handleCreateConnectionDelegate" aspect="org.jboss.jms.client.container.StateCreationAspect"/>
-   </bind>  
-   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate->createConnectionDelegate(..))">
-      <advice name="handleCreateConnectionDelegate" aspect="org.jboss.jms.client.container.HAAspect"/>
    </bind>
-               
-   <!-- Connection Stack -->   
+
+   <!--
+        Connection Stack
+   -->
+
+   <!-- It is important that FailoverAspect intercepts performFailover() before FailoverValveInterceptor -->
+   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->performFailover())">
+      <advice name="handlePerformFailover" aspect="org.jboss.jms.client.container.FailoverAspect"/>
+   </bind>
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->$implementing{org.jboss.jms.delegate.ConnectionDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.FailoverValveInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>      
       <interceptor-ref name="org.jboss.jms.client.container.ClosedInterceptor"/>               
    </bind>  
@@ -66,15 +88,24 @@
    </bind>         
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->close())">
       <advice name="handleClose" aspect="org.jboss.jms.client.container.ConnectionAspect"/>
-   </bind>   
+   </bind>
+   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->registerFailoverListener(..))">
+      <advice name="handleRegisterFailoverListener" aspect="org.jboss.jms.client.container.ConnectionAspect"/>
+   </bind>
+   <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->unregisterFailoverListener(..))">
+      <advice name="handleUnregisterFailoverListener" aspect="org.jboss.jms.client.container.ConnectionAspect"/>
+   </bind>
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->createSessionDelegate(..))">
       <advice name="handleCreateSessionDelegate" aspect="org.jboss.jms.client.container.StateCreationAspect"/>
    </bind>
-    
 
-   <!-- Session Stack -->   
+   <!--
+        Session Stack
+   -->
+
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientSessionDelegate->$implementing{org.jboss.jms.delegate.SessionDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.FailoverValveInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>      
       <interceptor-ref name="org.jboss.jms.client.container.ClosedInterceptor"/>       
    </bind>
@@ -166,9 +197,13 @@
       <advice name="handleCreateBrowserDelegate" aspect="org.jboss.jms.client.container.StateCreationAspect"/>
    </bind>
    
-   <!-- Consumer Stack -->   
+   <!--
+        Consumer Stack
+   -->
+
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientConsumerDelegate->$implementing{org.jboss.jms.delegate.ConsumerDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.FailoverValveInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>      
       <interceptor-ref name="org.jboss.jms.client.container.ClosedInterceptor"/>          
    </bind>       
@@ -198,9 +233,12 @@
    </bind>   
 
    
-   <!-- Producer Stack -->   
+   <!--
+        Producer Stack
+   -->   
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientProducerDelegate->$implementing{org.jboss.jms.delegate.ProducerDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.FailoverValveInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>      
       <interceptor-ref name="org.jboss.jms.client.container.ClosedInterceptor"/>  
    </bind>        
@@ -250,10 +288,12 @@
       <advice name="handleClose" aspect="org.jboss.jms.client.container.ProducerAspect"/>
    </bind>     
 
-   
-   <!-- Browser Stack -->   
+   <!--
+        Browser Stack
+   -->
    <bind pointcut="execution(* org.jboss.jms.client.delegate.ClientBrowserDelegate->$implementing{org.jboss.jms.delegate.BrowserDelegate}(..))">
       <interceptor-ref name="org.jboss.jms.client.container.ClientLogInterceptor"/>
+      <interceptor-ref name="org.jboss.jms.client.container.FailoverValveInterceptor"/>
       <interceptor-ref name="org.jboss.jms.client.container.ExceptionInterceptor"/>      
       <interceptor-ref name="org.jboss.jms.client.container.ClosedInterceptor"/>
    </bind>      

Modified: trunk/src/etc/prepare-aop.xml
===================================================================
--- trunk/src/etc/prepare-aop.xml	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/etc/prepare-aop.xml	2006-12-31 10:51:38 UTC (rev 1880)
@@ -6,6 +6,7 @@
 <aop>
 
   <!-- Prepare the client stub classes - these provide the client side advice stack -->
+  <prepare expr="execution(* org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate->$implementing{org.jboss.jms.delegate.ConnectionFactoryDelegate}(..))"/>
   <prepare expr="execution(* org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate->$implementing{org.jboss.jms.delegate.ConnectionFactoryDelegate}(..))"/>
   <prepare expr="execution(* org.jboss.jms.client.delegate.ClientConnectionDelegate->$implementing{org.jboss.jms.delegate.ConnectionDelegate}(..))"/>
   <prepare expr="execution(* org.jboss.jms.client.delegate.ClientSessionDelegate->$implementing{org.jboss.jms.delegate.SessionDelegate}(..))"/>
@@ -14,6 +15,7 @@
   <prepare expr="execution(* org.jboss.jms.client.delegate.ClientBrowserDelegate->$implementing{org.jboss.jms.delegate.BrowserDelegate}(..))"/>
  
   <!-- Prepare the server delegate classes - these provide the server side advice stack -->
+  <prepare expr="execution(* org.jboss.jms.server.endpoint.advised.ClientAOPStackProviderAdvised->$implementing{org.jboss.jms.client.ClientAOPStackProvider}(..))"/>
   <prepare expr="execution(* org.jboss.jms.server.endpoint.advised.ConnectionFactoryAdvised->$implementing{org.jboss.jms.server.endpoint.ConnectionFactoryEndpoint}(..))"/>
   <prepare expr="execution(* org.jboss.jms.server.endpoint.advised.ConnectionAdvised->$implementing{org.jboss.jms.server.endpoint.ConnectionEndpoint}(..))"/>
   <prepare expr="execution(* org.jboss.jms.server.endpoint.advised.SessionAdvised->$implementing{org.jboss.jms.server.endpoint.SessionEndpoint}(..))"/> 

Added: trunk/src/main/org/jboss/jms/client/ClientAOPStackLoader.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/ClientAOPStackLoader.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/ClientAOPStackLoader.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,80 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client;
+
+import org.jboss.jms.client.container.JmsClientAspectXMLLoader;
+
+/**
+ * A static singleton that insures the client-side AOP stack is loaded.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public class ClientAOPStackLoader
+{
+   // Constants -----------------------------------------------------
+
+   // Static --------------------------------------------------------
+
+   public static ClientAOPStackLoader getInstance()
+   {
+      synchronized(ClientAOPStackLoader.class)
+      {
+         if (instance == null)
+         {
+            instance = new ClientAOPStackLoader();
+         }
+         return instance;
+      }
+   }
+
+   // Attributes ----------------------------------------------------
+
+   private static ClientAOPStackLoader instance;
+
+   private boolean loaded;
+
+   // Constructors --------------------------------------------------
+
+   private ClientAOPStackLoader()
+   {
+      loaded = false;
+   }
+
+   // Public --------------------------------------------------------
+
+   /**
+    * @param delegate - either an instance of ClientClusteredConnectionFactoryDelegate or
+    *        ClientConnectionFactoryDelegate.
+    *
+    * @throws Exception - if something goes wrong with downloading the AOP configuration from the
+    *         server and installing it.
+    */
+   public synchronized void load(ClientAOPStackProvider delegate) throws Exception
+   {
+      if (loaded)
+      {
+         return;
+      }
+
+      byte[] clientAOPStack = delegate.getClientAOPStack();
+
+      new JmsClientAspectXMLLoader().deployXML(clientAOPStack);
+
+      loaded = true;
+   }
+
+   // Package protected ---------------------------------------------
+
+   // Protected -----------------------------------------------------
+
+   // Private -------------------------------------------------------
+
+   // Inner classes -------------------------------------------------
+}

Added: trunk/src/main/org/jboss/jms/client/ClientAOPStackProvider.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/ClientAOPStackProvider.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/ClientAOPStackProvider.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,23 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client;
+
+/**
+ * An interface implemented by an object that can provide a client-side AOP stack configuration.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public interface ClientAOPStackProvider
+{
+   /**
+    * @return the AOP stack as byte[]. Use JmsClientAspectXMLLoader to deploy it on the client.
+    */
+   byte[] getClientAOPStack();
+}

Added: trunk/src/main/org/jboss/jms/client/FailoverEvent.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/FailoverEvent.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/FailoverEvent.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,74 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client;
+
+import java.util.EventObject;
+
+/**
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ * $Id$
+ */
+public class FailoverEvent extends EventObject
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   public static final int FAILURE_DETECTED = 10;
+   public static final int FAILOVER_STARTED = 20;
+   public static final int FAILOVER_COMPLETED = 30;
+   public static final int FAILOVER_FAILED = 100;
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private int type;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   public FailoverEvent(int type, Object source)
+   {
+      super(source);
+      checkType(type);
+      this.type = type;
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public int getType()
+   {
+      return type;
+   }
+
+   public String toString()
+   {
+      return
+         type == FAILURE_DETECTED ? "FAILURE_DETECTED" :
+            type == FAILOVER_STARTED ? "FAILOVER_STARTED" :
+               type == FAILOVER_COMPLETED ? "FAILOVER_COMPLETED" :
+                  type == FAILOVER_FAILED ? "FAILOVER_FAILED" : "UNKNOWN_FAILOVER_EVENT";
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   private void checkType(int type) throws IllegalArgumentException
+   {
+      if (type != FAILURE_DETECTED &&
+         type != FAILOVER_STARTED &&
+         type != FAILOVER_COMPLETED &&
+         type != FAILOVER_FAILED)
+      {
+         throw new IllegalArgumentException("Illegal failover event type: " + type);
+      }
+   }
+
+   // Inner classes --------------------------------------------------------------------------------
+}

Added: trunk/src/main/org/jboss/jms/client/FailoverListener.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/FailoverListener.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/FailoverListener.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,18 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client;
+
+/**
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public interface FailoverListener
+{
+   void failoverEventOccured(FailoverEvent event);
+}

Modified: trunk/src/main/org/jboss/jms/client/JBossConnection.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/JBossConnection.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/JBossConnection.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -45,12 +45,11 @@
 import javax.jms.XATopicSession;
 
 import org.jboss.jms.client.delegate.ClientConnectionDelegate;
-import org.jboss.jms.client.remoting.JMSRemotingConnection;
+import org.jboss.jms.client.delegate.DelegateSupport;
 import org.jboss.jms.client.state.ConnectionState;
 import org.jboss.jms.delegate.ConnectionDelegate;
 import org.jboss.jms.delegate.SessionDelegate;
 import org.jboss.jms.util.ThreadContextClassLoaderChanger;
-import org.jboss.logging.Logger;
 
 /**
  * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
@@ -64,24 +63,21 @@
     XAConnection, XAQueueConnection, XATopicConnection, Serializable
 {
 
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
    private static final long serialVersionUID = -3715868654823177898L;
    
-   private static final Logger log = Logger.getLogger(JBossConnection.class);
-   
-
    static final int TYPE_GENERIC_CONNECTION = 0;
    static final int TYPE_QUEUE_CONNECTION = 1;
    static final int TYPE_TOPIC_CONNECTION = 2;
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
    protected ConnectionDelegate delegate;
    private int connectionType;
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
    public JBossConnection(ConnectionDelegate delegate, int connectionType)
    {
@@ -89,7 +85,7 @@
       this.connectionType = connectionType;
    }
 
-   // Connection implementation -------------------------------------
+   // Connection implementation --------------------------------------------------------------------
 
    public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException
    {
@@ -158,11 +154,11 @@
          String msg = "Cannot create a durable connection consumer on a QueueConnection";
          throw new IllegalStateException(msg);
       }
-      return delegate.
-         createConnectionConsumer(topic, subscriptionName, messageSelector, sessionPool, maxMessages);
+      return delegate.createConnectionConsumer(topic, subscriptionName, messageSelector,
+                                               sessionPool, maxMessages);
    }
 
-   // QueueConnection implementation ---------------------------------
+   // QueueConnection implementation ---------------------------------------------------------------
 
    public QueueSession createQueueSession(boolean transacted,
                                           int acknowledgeMode) throws JMSException
@@ -175,10 +171,11 @@
                                                       ServerSessionPool sessionPool,
                                                       int maxMessages) throws JMSException
     {
-      return delegate.createConnectionConsumer(queue, null, messageSelector, sessionPool, maxMessages);
+      return delegate.
+         createConnectionConsumer(queue, null, messageSelector, sessionPool, maxMessages);
     }
 
-   // TopicConnection implementation ---------------------------------
+   // TopicConnection implementation ---------------------------------------------------------------
 
    public TopicSession createTopicSession(boolean transacted,
                                           int acknowledgeMode) throws JMSException
@@ -191,10 +188,11 @@
                                                       ServerSessionPool sessionPool,
                                                       int maxMessages) throws JMSException
    {
-      return delegate.createConnectionConsumer(topic, null, messageSelector, sessionPool, maxMessages);
+      return delegate.
+         createConnectionConsumer(topic, null, messageSelector, sessionPool, maxMessages);
    }
 
-   // XAConnection implementation -------------------------------------
+   // XAConnection implementation ------------------------------------------------------------------
 
    public XASession createXASession() throws JMSException
    {
@@ -202,7 +200,7 @@
                                     JBossSession.TYPE_GENERIC_SESSION);
    }
 
-   // XAQueueConnection implementation ---------------------------------
+   // XAQueueConnection implementation -------------------------------------------------------------
 
    public XAQueueSession createXAQueueSession() throws JMSException
    {
@@ -211,7 +209,7 @@
 
    }
 
-   // XATopicConnection implementation ---------------------------------
+   // XATopicConnection implementation -------------------------------------------------------------
 
    public XATopicSession createXATopicSession() throws JMSException
    {
@@ -220,13 +218,8 @@
 
    }
 
-   // Public --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
 
-   public String toString()
-   {
-      return "JBossConnection->" + delegate;
-   }
-
    public String getRemotingClientSessionId()
    {
       ConnectionState state = (ConnectionState)((ClientConnectionDelegate)delegate).getState();
@@ -239,10 +232,33 @@
       return delegate;
    }
 
-   // Package protected ---------------------------------------------
+   /**
+    * Convenience method.
+    */
+   public int getServerID()
+   {
+      return ((ConnectionState)((DelegateSupport)delegate).getState()).getServerID();
+   }
 
-   // Protected -----------------------------------------------------
+   public void registerFailoverListener(FailoverListener listener)
+   {
+      delegate.registerFailoverListener(listener);
+   }
 
+   public synchronized boolean unregisterFailoverListener(FailoverListener listener)
+   {
+      return delegate.unregisterFailoverListener(listener);
+   }
+
+   public String toString()
+   {
+      return "JBossConnection->" + delegate;
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
    protected JBossSession createSessionInternal(boolean transacted, int acknowledgeMode,
                                                 boolean isXA, int type) throws JMSException
    {
@@ -283,7 +299,7 @@
 //      }
 //   }
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner classes -------------------------------------------------
+   // Inner classes --------------------------------------------------------------------------------
 }

Modified: trunk/src/main/org/jboss/jms/client/JBossConnectionFactory.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/JBossConnectionFactory.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/JBossConnectionFactory.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -39,13 +39,11 @@
 import javax.naming.NamingException;
 import javax.naming.Reference;
 
-import org.jboss.aop.Advised;
-import org.jboss.jms.client.container.JmsClientAspectXMLLoader;
-import org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate;
 import org.jboss.jms.delegate.ConnectionFactoryDelegate;
 import org.jboss.jms.referenceable.SerializableObjectRefAddr;
 import org.jboss.jms.server.endpoint.CreateConnectionResult;
 import org.jboss.jms.util.ThreadContextClassLoaderChanger;
+import org.jboss.jms.client.delegate.Initializable;
 import org.jboss.logging.Logger;
 
 /**
@@ -56,35 +54,30 @@
  * $Id$
  */
 public class JBossConnectionFactory implements
-               ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
-               XAConnectionFactory, XAQueueConnectionFactory, XATopicConnectionFactory,
-               Serializable/*, Referenceable http://jira.jboss.org/jira/browse/JBMESSAGING-395*/
+   ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
+   XAConnectionFactory, XAQueueConnectionFactory, XATopicConnectionFactory,
+   Serializable/*, Referenceable http://jira.jboss.org/jira/browse/JBMESSAGING-395*/
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
    
    private final static long serialVersionUID = -2810634789345348326L;
    
    private static final Logger log = Logger.getLogger(JBossConnectionFactory.class);
+
+   // Static ---------------------------------------------------------------------------------------
    
+   // Attributes -----------------------------------------------------------------------------------
    
-   // Static --------------------------------------------------------
-   
-   private static boolean configLoaded;
-   
-   // Attributes ----------------------------------------------------
-   
    protected ConnectionFactoryDelegate delegate;
+
+   // Constructors ---------------------------------------------------------------------------------
    
-   private boolean initialised;
-   
-   // Constructors --------------------------------------------------
-   
    public JBossConnectionFactory(ConnectionFactoryDelegate delegate)
    {
       this.delegate = delegate;      
    }
    
-   // ConnectionFactory implementation ------------------------------
+   // ConnectionFactory implementation -------------------------------------------------------------
    
    public Connection createConnection() throws JMSException
    {
@@ -93,34 +86,39 @@
    
    public Connection createConnection(String username, String password) throws JMSException
    {
-      return createConnectionInternal(username, password, false, JBossConnection.TYPE_GENERIC_CONNECTION);
+      return createConnectionInternal(username, password, false,
+                                      JBossConnection.TYPE_GENERIC_CONNECTION);
    }
    
-   // QueueConnectionFactory implementation -------------------------
+   // QueueConnectionFactory implementation --------------------------------------------------------
    
    public QueueConnection createQueueConnection() throws JMSException
    {
       return createQueueConnection(null, null);
    }
    
-   public QueueConnection createQueueConnection(String username, String password) throws JMSException
+   public QueueConnection createQueueConnection(String username, String password)
+      throws JMSException
    {
-      return createConnectionInternal(username, password, false, JBossConnection.TYPE_QUEUE_CONNECTION);
+      return createConnectionInternal(username, password, false,
+                                      JBossConnection.TYPE_QUEUE_CONNECTION);
    }
    
-   // TopicConnectionFactory implementation -------------------------
+   // TopicConnectionFactory implementation --------------------------------------------------------
    
    public TopicConnection createTopicConnection() throws JMSException
    {
       return createTopicConnection(null, null);
    }
    
-   public TopicConnection createTopicConnection(String username, String password) throws JMSException
+   public TopicConnection createTopicConnection(String username, String password)
+      throws JMSException
    {
-      return createConnectionInternal(username, password, false, JBossConnection.TYPE_TOPIC_CONNECTION);
+      return createConnectionInternal(username, password, false,
+                                      JBossConnection.TYPE_TOPIC_CONNECTION);
    }
    
-   // XAConnectionFactory implementation ------------------------------
+   // XAConnectionFactory implementation -----------------------------------------------------------
    
    public XAConnection createXAConnection() throws JMSException
    {
@@ -129,34 +127,39 @@
    
    public XAConnection createXAConnection(String username, String password) throws JMSException
    {
-      return createConnectionInternal(username, password, true, JBossConnection.TYPE_GENERIC_CONNECTION);   
+      return createConnectionInternal(username, password, true,
+                                      JBossConnection.TYPE_GENERIC_CONNECTION);
    }
    
-   // XAQueueConnectionFactory implementation -------------------------
+   // XAQueueConnectionFactory implementation ------------------------------------------------------
    
    public XAQueueConnection createXAQueueConnection() throws JMSException
    {
       return createXAQueueConnection(null, null);
    }
    
-   public XAQueueConnection createXAQueueConnection(String username, String password) throws JMSException
+   public XAQueueConnection createXAQueueConnection(String username, String password)
+      throws JMSException
    {
-      return createConnectionInternal(username, password, true, JBossConnection.TYPE_QUEUE_CONNECTION);   
+      return createConnectionInternal(username, password, true,
+                                      JBossConnection.TYPE_QUEUE_CONNECTION);
    }
    
-   // XATopicConnectionFactory implementation -------------------------
+   // XATopicConnectionFactory implementation ------------------------------------------------------
    
    public XATopicConnection createXATopicConnection() throws JMSException
    {
       return createXATopicConnection(null, null);
    }
    
-   public XATopicConnection createXATopicConnection(String username, String password) throws JMSException
+   public XATopicConnection createXATopicConnection(String username, String password)
+      throws JMSException
    {
-      return createConnectionInternal(username, password, true, JBossConnection.TYPE_TOPIC_CONNECTION);   
+      return createConnectionInternal(username, password, true,
+                                      JBossConnection.TYPE_TOPIC_CONNECTION);
    }
    
-   // Referenceable implementation ---------------------------------------
+   // Referenceable implementation -----------------------------------------------------------------
    
    public Reference getReference() throws NamingException
    {
@@ -166,7 +169,7 @@
                null);
    }
    
-   // Public --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
    
    public String toString()
    {
@@ -178,24 +181,34 @@
       return delegate;
    }
    
-   // Package protected ---------------------------------------------
+   // Package protected ----------------------------------------------------------------------------
    
-   // Protected -----------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
    
    protected JBossConnection createConnectionInternal(String username, String password,
-            boolean isXA, int type)
+                                                      boolean isXA, int type)
       throws JMSException
    {
-      
       ThreadContextClassLoaderChanger tccc = new ThreadContextClassLoaderChanger();
       
       try
       {
          tccc.set(getClass().getClassLoader());
-         
-         ensureAOPConfigLoaded((ClientConnectionFactoryDelegate)delegate);
-         initDelegate();
-         
+
+         try
+         {
+            ClientAOPStackLoader.getInstance().load(delegate);
+         }
+         catch(Exception e)
+         {
+            // Need to log message since no guarantee that client will log it
+            final String msg = "Failed to download and/or install client side AOP stack";
+            log.error(msg, e);
+            throw new RuntimeException(msg, e);
+         }
+
+         ((Initializable)delegate).init();
+
          // The version used by the connection is the minimum of the server version for the
          // connection factory and the client code version
          
@@ -209,50 +222,7 @@
       }
    }
    
-   protected synchronized void initDelegate()
-   {
-      if (!initialised)
-      {
-         ((ClientConnectionFactoryDelegate)delegate).init();
-         initialised = true;
-      }         
-   }
-   
-   protected void ensureAOPConfigLoaded(ClientConnectionFactoryDelegate delegate)
-   {
-      try
-      {
-         synchronized (JBossConnectionFactory.class)
-         {
-            if (!configLoaded)
-            {
-               // Load the client side aspect stack configuration from the server and apply it
-               
-               delegate.init();
-               
-               byte[] clientAOPConfig = delegate.getClientAOPConfig();
-               
-               // Remove interceptor since we don't want it on the front of the stack
-               ((Advised)delegate)._getInstanceAdvisor().removeInterceptor(delegate.getName());
-               
-               JmsClientAspectXMLLoader loader = new JmsClientAspectXMLLoader();
-               
-               loader.deployXML(clientAOPConfig);
-               
-               configLoaded = true;               
-            }
-         }   
-      }
-      catch(Exception e)
-      {
-         // Need to log message since no guarantee that client will log it
-         final String msg = "Failed to config client side AOP";
-         log.error(msg, e);
-         throw new RuntimeException(msg, e);
-      }
-   }
-   
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
       
-   // Inner classes -------------------------------------------------
+   // Inner classes --------------------------------------------------------------------------------
 }

Modified: trunk/src/main/org/jboss/jms/client/JBossMessageProducer.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/JBossMessageProducer.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/JBossMessageProducer.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -45,7 +45,7 @@
  *
  * $Id$
  */
-class JBossMessageProducer implements MessageProducer, QueueSender, TopicPublisher, Serializable
+public class JBossMessageProducer implements MessageProducer, QueueSender, TopicPublisher, Serializable
 {
    // Constants -----------------------------------------------------
    
@@ -226,6 +226,11 @@
    
    // Public --------------------------------------------------------
 
+   public ProducerDelegate getDelegate()
+   {
+      return delegate;
+   }
+
    public String toString()
    {
       return "JBossMessageProducer->" + delegate;

Modified: trunk/src/main/org/jboss/jms/client/JBossQueueBrowser.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/JBossQueueBrowser.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/JBossQueueBrowser.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -35,7 +35,7 @@
  *
  * $Id$
  */
-class JBossQueueBrowser implements QueueBrowser, Serializable
+public class JBossQueueBrowser implements QueueBrowser, Serializable
 {
    // Constants -----------------------------------------------------
 
@@ -90,6 +90,11 @@
       return "JBossQueueBrowser->" + delegate;
    }
 
+   public BrowserDelegate getDelegate()
+   {
+      return delegate;
+   }
+
    // Package protected ---------------------------------------------
 
    // Protected -----------------------------------------------------

Added: trunk/src/main/org/jboss/jms/client/Valve.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/Valve.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/Valve.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,38 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client;
+
+/**
+ * An interface that controls the permeability of implementing objects.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ * $Id$
+ */
+public interface Valve
+{
+   /**
+    * "Closes" the valve. The implementing object becomes impermeable. Did't go for "close()" to
+    * avoid name conflicts with Closeable.close().
+    */
+   void closeValve() throws Exception;
+
+   /**
+    * "Opens" the valve. The implementing object becomes permeable. Didn't go for "open()" for
+    * symmetry reasons.
+    */
+   void openValve() throws Exception;
+
+   boolean isValveOpen();
+
+   /**
+    * @return the number of threads currenty "penetrating" the open valve. Shold be 0 if the valve
+    *         is closed.
+    */
+   int getActiveThreadsCount();
+
+}

Modified: trunk/src/main/org/jboss/jms/client/container/ClosedInterceptor.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ClosedInterceptor.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ClosedInterceptor.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -126,7 +126,7 @@
          id = DelegateIdentity.getIdentity(invocation);
       }
 
-      String methodName = ((MethodInvocation) invocation).getMethod().getName();
+      String methodName = ((MethodInvocation)invocation).getMethod().getName();
 
       if ("isClosed".equals(methodName))
       {

Added: trunk/src/main/org/jboss/jms/client/container/ClusteringAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ClusteringAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ClusteringAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,254 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client.container;
+
+import org.jboss.logging.Logger;
+import org.jboss.aop.joinpoint.Invocation;
+import org.jboss.aop.joinpoint.MethodInvocation;
+import org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate;
+import org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate;
+import org.jboss.jms.client.delegate.ClientConnectionDelegate;
+import org.jboss.jms.client.delegate.DelegateSupport;
+import org.jboss.jms.client.state.ConnectionState;
+import org.jboss.jms.server.endpoint.CreateConnectionResult;
+
+import javax.jms.JMSException;
+import java.util.Map;
+
+/**
+ * This aspect is part of a clustered ConnectionFactory aspect stack.
+ * Some of its responsibilities are:
+ *
+ * - To choose the next node to create a physical connection to, based on a pluggable load balancing
+ *   policy.
+ * - To handle physical connection creation (by delegating it to a non-clustered ConnectionFactory
+ *   delegate) and instal failure listeners.
+ * - etc.
+ *
+ * It's a PER_INSTANCE aspect (one of these per each clustered ConnectionFactory instance)
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ *
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public class ClusteringAspect
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   private static final Logger log = Logger.getLogger(ClusteringAspect.class);
+
+   public static final int MAX_RECONNECT_HOP_COUNT = 10;
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   // This is a PER_INSTANCE aspect, so it has a 1-to-1 relationship with its delegate
+   private ClientClusteredConnectionFactoryDelegate clusteredDelegate;
+
+   // The index of the next non-clustered delegate to be used. Only access it from a synchronized
+   // block. Currently hardcoded round-robin, the algorithm must be made pluggable.
+   private int next;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   public ClusteringAspect()
+   {
+      next = 0;
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public CreateConnectionResult handleCreateConnectionDelegate(Invocation invocation)
+      throws Throwable
+   {
+      // initalize this PER_INSTANCE aspect by getting a hold of its corresponding clustered
+      // delegate and maintaining a reference to it
+      if (clusteredDelegate == null)
+      {
+         clusteredDelegate = (ClientClusteredConnectionFactoryDelegate)invocation.getTargetObject();
+      }
+
+      // the method handles both the case of a first connection creation attempt and a re-try during
+      // a client-side failover. The difference is given by the failedNodeID (-1 for first attempt)
+
+      MethodInvocation mi = (MethodInvocation)invocation;
+      String username = (String)mi.getArguments()[0];
+      String password = (String)mi.getArguments()[1];
+      Integer failedNodeID = (Integer)mi.getArguments()[2];
+
+      // We attempt to connect to the next node in a loop, since we might need to go through
+      // multiple hops
+
+      int attemptCount = 0;
+      ClientConnectionFactoryDelegate delegate = null;
+
+      while (attemptCount < MAX_RECONNECT_HOP_COUNT)
+      {
+         if (delegate == null)
+         {
+            if (failedNodeID != null && failedNodeID.intValue() >= 0)
+            {
+               delegate = getFailoverDelegateForNode(failedNodeID);
+            }
+            else
+            {
+               delegate = getNextRoundRobinDelegate();
+            }
+         }
+
+         log.debug(this + " has chosen " + delegate + " as target, " +
+            (attemptCount == 0 ? "first connection attempt" : attemptCount + " connection attempts"));
+
+         CreateConnectionResult res = delegate.createConnectionDelegate(username, password, -1); // TODO get rid of -1 from signature
+         ClientConnectionDelegate cd = (ClientConnectionDelegate)res.getDelegate();
+
+         if (cd != null)
+         {
+            // valid connection
+
+            log.debug(this + " got local connection delegate " + cd);
+
+            ConnectionState state = (ConnectionState)((DelegateSupport)cd).getState();
+
+            // add a connection listener to detect failure; the consolidated remoting connection
+            // listener must be already in place and configured
+            state.getRemotingConnectionListener().
+               addDelegateListener(new ConnectionFailureListener(cd));
+
+            log.debug(this + " installed failure listener on " + cd);
+
+            // also cache the username and the password into state, useful in case FailoverAspect
+            // needs to create a new connection instead of a failed on
+            state.setUsername(username);
+            state.setPassword(password);
+
+            // also add a reference to the clustered ConnectionFactory delegate, useful in case
+            // FailoverAspect needs to create a new connection instead of a failed on
+            state.setClusteredConnectionFactoryDeleage(clusteredDelegate);
+
+            return new CreateConnectionResult(cd);
+         }
+         else
+         {
+            // we did not get a valid connection to the node we've just tried
+
+            int actualServerID = res.getActualFailoverNodeID();
+
+            if (actualServerID == -1)
+            {
+               // No failover attempt was detected on the server side; this might happen if the
+               // client side network fails temporarily so the client connection breaks but the
+               // server cluster is still up and running - in this case we don't perform failover.
+
+               // TODO Is this the right thing to do?
+
+               log.warn("Client attempted failover, but no failover attempt " +
+                        "has been detected on the server side.");
+
+               return null;
+            }
+
+            // Server side failover has occurred / is occurring but trying to go to the 'default'
+            // failover node did not succeed. Retry with the node suggested by the cluster.
+
+            attemptCount++;
+
+            delegate = getDelegateForNode(actualServerID);
+
+            if (delegate == null)
+            {
+               // the delegate corresponding to the actualServerID not found among the cached
+               // delegates. TODO Could this ever happen? Should we send back the cf, or update it
+               // instead of just the id??
+               throw new JMSException("Cannot find a cached connection factory delegate for " +
+                  "node " + actualServerID);
+            }
+         }
+      }
+
+      throw new JMSException("Maximum number of failover attempts exceeded. " +
+                             "Cannot find a server to failover onto.");
+   }
+
+   public String toString()
+   {
+      return "ClusteringAspect[" + clusteredDelegate + "]";
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+
+   /**
+    * TODO This is currently hardcoded as round robin. The policy should be pluggable.
+    */
+   private synchronized ClientConnectionFactoryDelegate getNextRoundRobinDelegate()
+   {
+      ClientConnectionFactoryDelegate[] delegates = clusteredDelegate.getDelegates();
+
+      if (next >= delegates.length)
+      {
+         next = 0;
+      }
+
+      ClientConnectionFactoryDelegate delegate = delegates[next++];
+
+         if (next >= delegates.length)
+         {
+            next = 0;
+         }
+
+      return delegate;
+   }
+
+   private synchronized ClientConnectionFactoryDelegate getFailoverDelegateForNode(Integer nodeID)
+   {
+      ClientConnectionFactoryDelegate[] delegates = clusteredDelegate.getDelegates();
+
+      if (nodeID.intValue() < 0)
+      {
+         throw new IllegalArgumentException("nodeID must be 0 or positive");
+      }
+
+      Map failoverMap = clusteredDelegate.getFailoverMap();
+      Integer failoverNodeID = (Integer)failoverMap.get(nodeID);
+
+      for (int i = 0; i < delegates.length; i++)
+      {
+         if (delegates[i].getServerID() == failoverNodeID.intValue())
+         {
+            return delegates[i];
+         }
+      }
+
+      return null;
+   }
+
+   private synchronized ClientConnectionFactoryDelegate getDelegateForNode(int nodeID)
+   {
+      ClientConnectionFactoryDelegate[] delegates = clusteredDelegate.getDelegates();
+
+      for (int i = 0; i < delegates.length; i++)
+      {
+         if (delegates[i].getServerID() == nodeID)
+         {
+            return delegates[i];
+         }
+      }
+
+      return null;
+   }
+
+   // Inner classes --------------------------------------------------------------------------------
+
+}

Modified: trunk/src/main/org/jboss/jms/client/container/ConnectionAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ConnectionAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ConnectionAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -27,6 +27,7 @@
 import org.jboss.aop.joinpoint.Invocation;
 import org.jboss.aop.joinpoint.MethodInvocation;
 import org.jboss.jms.client.JBossConnectionMetaData;
+import org.jboss.jms.client.FailoverListener;
 import org.jboss.jms.client.remoting.ConsolidatedRemotingConnectionListener;
 import org.jboss.jms.client.remoting.JMSRemotingConnection;
 import org.jboss.jms.client.delegate.ClientConnectionDelegate;
@@ -193,6 +194,30 @@
       return ret;
    }
 
+   public Object  handleRegisterFailoverListener(Invocation invocation) throws Throwable
+   {
+      ConnectionState state = getConnectionState(invocation);
+
+      MethodInvocation mi = (MethodInvocation)invocation;
+      FailoverListener listener = (FailoverListener)mi.getArguments()[0];
+
+      state.registerFailoverListener(listener);
+
+      return null;
+   }
+
+   public Object handleUnregisterFailoverListener(Invocation invocation) throws Throwable
+   {
+      ConnectionState state = getConnectionState(invocation);
+
+      MethodInvocation mi = (MethodInvocation)invocation;
+      FailoverListener listener = (FailoverListener)mi.getArguments()[0];
+
+      boolean result = state.unregisterFailoverListener(listener);
+
+      return new Boolean(result);
+   }
+
    public String toString()
    {
       StringBuffer sb = new StringBuffer("ConnectionAspect[");

Added: trunk/src/main/org/jboss/jms/client/container/ConnectionFailureListener.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ConnectionFailureListener.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ConnectionFailureListener.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,80 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client.container;
+
+import org.jboss.remoting.ConnectionListener;
+import org.jboss.remoting.Client;
+import org.jboss.logging.Logger;
+import org.jboss.jms.client.delegate.ClientConnectionDelegate;
+import org.jboss.jms.client.FailoverEvent;
+import org.jboss.jms.client.state.ConnectionState;
+
+/**
+ * The listener that detects a connection failure and initiates the failover process. Each physical
+ * connection created under the supervision of ClusteredAspect has one of these.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public class ConnectionFailureListener implements ConnectionListener
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   private static final Logger log = Logger.getLogger(ConnectionFailureListener.class);
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private ClientConnectionDelegate cd;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   ConnectionFailureListener(ClientConnectionDelegate cd)
+   {
+      this.cd = cd;
+   }
+
+   // ConnectionListener implementation ------------------------------------------------------------
+
+   public void handleConnectionException(Throwable throwable, Client client)
+   {
+      try
+      {
+         log.debug(this + " is being notified of connection failure: " + throwable);
+
+         // generate a FAILURE_DETECTED event
+         ((ConnectionState)cd.getState()).
+            broadcastFailoverEvent(new FailoverEvent(FailoverEvent.FAILURE_DETECTED, cd));
+
+         log.debug(this + " initiating client-side failover");
+
+         cd.performFailover();
+      }
+      catch (Throwable e)
+      {
+         log.error("Caught exception in handling failure", e);
+      }
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public String toString()
+   {
+      return "ConnectionFailureListener[" + cd + "]";
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner classes --------------------------------------------------------------------------------
+}

Modified: trunk/src/main/org/jboss/jms/client/container/ConsumerAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ConsumerAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ConsumerAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -33,8 +33,6 @@
 import org.jboss.jms.client.state.SessionState;
 import org.jboss.jms.delegate.ConsumerDelegate;
 import org.jboss.jms.delegate.SessionDelegate;
-import org.jboss.jms.server.endpoint.ServerSessionEndpoint;
-import org.jboss.logging.Logger;
 
 import EDU.oswego.cs.dl.util.concurrent.QueuedExecutor;
 
@@ -54,8 +52,6 @@
 {
    // Constants -----------------------------------------------------
    
-   private static final Logger log = Logger.getLogger(ConsumerAspect.class);
-   
    // Static --------------------------------------------------------
 
    // Attributes ----------------------------------------------------

Modified: trunk/src/main/org/jboss/jms/client/container/ExceptionInterceptor.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ExceptionInterceptor.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ExceptionInterceptor.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -76,7 +76,7 @@
       }
       catch (Throwable t)
       {
-         //We log everything else
+         // We log everything else
          
          log(t);
          

Added: trunk/src/main/org/jboss/jms/client/container/FailoverAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/FailoverAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/FailoverAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,119 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client.container;
+
+import org.jboss.aop.joinpoint.Invocation;
+import org.jboss.jms.client.delegate.ClientConnectionDelegate;
+import org.jboss.jms.client.state.ConnectionState;
+import org.jboss.jms.client.FailoverEvent;
+import org.jboss.jms.server.endpoint.CreateConnectionResult;
+import org.jboss.jms.delegate.ConnectionFactoryDelegate;
+import org.jboss.logging.Logger;
+
+/**
+ * PER_VM Aspect.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ * $Id$
+ */
+public class FailoverAspect
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   private static final Logger log = Logger.getLogger(FailoverAspect.class);
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public Object handlePerformFailover(Invocation invocation) throws Throwable
+   {
+      Object target = invocation.getTargetObject();
+
+      if(target instanceof ClientConnectionDelegate)
+      {
+         performConnectionFailover((ClientConnectionDelegate)target);
+      }
+
+      return null;
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   private void performConnectionFailover(ClientConnectionDelegate delegate) throws Exception
+   {
+      ConnectionState state = (ConnectionState)delegate.getState();
+
+      CreateConnectionResult res = null;
+      boolean failoverSuccessful = false;
+
+      try
+      {
+         // block any other invocations ariving while we're doing failover, on this delegate and
+         // recursively down the hierarchy
+
+         // WARNING - this may block if there are active invocations through valves!
+         delegate.closeValve();
+
+         log.debug("starting client-side failover");
+
+         // generate a FAILOVER_STARTED event. The event must be broadcasted AFTER valve closure,
+         // to insure the client-side stack is in a deterministic state
+         state.broadcastFailoverEvent(new FailoverEvent(FailoverEvent.FAILOVER_STARTED, delegate));
+
+         int failedNodeID = state.getServerID();
+         ConnectionFactoryDelegate clusteredDelegate =
+            state.getClusteredConnectionFactoryDelegate();
+
+         // re-try creating the connection
+         res = clusteredDelegate.
+            createConnectionDelegate(state.getUsername(), state.getPassword(), failedNodeID);
+
+         if (res == null)
+         {
+            // No failover attempt was detected on the server side; this might happen if the
+            // client side network fails temporarily so the client connection breaks but the
+            // server cluster is still up and running - in this case we don't perform failover.
+            failoverSuccessful = false;
+         }
+         else
+         {
+            // recursively synchronize state
+            ClientConnectionDelegate newDelegate = (ClientConnectionDelegate)res.getDelegate();
+            delegate.synchronizeWith(newDelegate);
+            failoverSuccessful = true;
+         }
+      }
+      finally
+      {
+         if (failoverSuccessful)
+         {
+            state.broadcastFailoverEvent(new FailoverEvent(FailoverEvent.FAILOVER_COMPLETED,
+                                                           res.getDelegate()));
+         }
+         else
+         {
+            state.broadcastFailoverEvent(new FailoverEvent(FailoverEvent.FAILOVER_FAILED,
+                                                           delegate));
+         }
+
+         // failover done, open valves
+         delegate.openValve();
+      }
+   }
+
+   // Inner classes --------------------------------------------------------------------------------
+}

Added: trunk/src/main/org/jboss/jms/client/container/FailoverValveInterceptor.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/FailoverValveInterceptor.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/FailoverValveInterceptor.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,229 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client.container;
+
+import org.jboss.aop.advice.Interceptor;
+import org.jboss.aop.joinpoint.Invocation;
+import org.jboss.aop.joinpoint.MethodInvocation;
+import org.jboss.logging.Logger;
+import org.jboss.jms.client.delegate.DelegateSupport;
+import org.jboss.jms.client.state.HierarchicalState;
+import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
+import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
+import EDU.oswego.cs.dl.util.concurrent.Sync;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * An interceptor that acts as a failover valve: it allows all invocations to go through as long
+ * as there is no failover in progress (valve is open), and holds all invocations while client-side
+ * failover is taking place (valve is closed). The interceptor fields org.jboss.jms.client.Valve's
+ * method calls.
+ *
+ * It is a PER_INSTANCE interceptor.
+ *
+ * An instance of this interceptor must guard access to each connection, session, producer, consumer
+ * and queue browser delegate.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ * $Id$
+ */
+public class FailoverValveInterceptor implements Interceptor
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   private static final Logger log = Logger.getLogger(FailoverValveInterceptor.class);
+
+   // Static ---------------------------------------------------------------------------------------
+
+   private static boolean trace = log.isTraceEnabled();
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private DelegateSupport delegate;
+   private HierarchicalState state;
+   private volatile boolean valveOpen;
+
+   private ReadWriteLock rwLock;
+
+   // the number of threads currently "penetrating" the open valve
+   private int activeThreadsCount;
+
+   // only for tracing
+   private List activeMethods;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   public FailoverValveInterceptor()
+   {
+      valveOpen = true;
+      activeThreadsCount = 0;
+      rwLock = new WriterPreferenceReadWriteLock();
+      if (trace)
+      {
+         activeMethods = new ArrayList();
+      }
+   }
+
+   // Interceptor implemenation --------------------------------------------------------------------
+
+   public String getName()
+   {
+      return "FailoverValveInterceptor";
+   }
+
+   public Object invoke(Invocation invocation) throws Throwable
+   {
+      // maintain a reference to the delegate that sends invocation through this interceptor. It
+      // makes sense, since it's an PER_INSTANCE interceptor.
+      if (delegate == null)
+      {
+         delegate = (DelegateSupport)invocation.getTargetObject();
+         state = delegate.getState();
+      }
+
+      String methodName = ((MethodInvocation)invocation).getMethod().getName();
+      Sync writeLock =  rwLock.writeLock();
+      Sync readLock = rwLock.readLock();
+
+      if("closeValve".equals(methodName))
+      {
+         if (!valveOpen)
+         {
+            // valve already closed, this is a noop
+            log.warn(this + " already closed!");
+            return null;
+         }
+
+         state.closeChildrensValves();
+
+         boolean acquired = false;
+
+         while(!acquired)
+         {
+            try
+            {
+               acquired = writeLock.attempt(500);
+            }
+            catch(InterruptedException e)
+            {
+               // OK
+            }
+
+            if (!acquired)
+            {
+               log.debug(this + " failed to close");
+            }
+         }
+
+         valveOpen = false;
+
+         log.debug(this + " has been closed");
+
+         return null;
+      }
+      else if("openValve".equals(methodName))
+      {
+         if (valveOpen)
+         {
+            // valve already open, this is a noop
+            log.warn(this + " already open!");
+            return null;
+         }
+
+         state.openChildrensValves();
+
+         writeLock.release();
+         valveOpen = true;
+
+         log.debug(this + " has been opened");
+
+         return null;
+      }
+      else if("isValveOpen".equals(methodName))
+      {
+         if (valveOpen)
+         {
+            return Boolean.TRUE;
+         }
+         else
+         {
+            return Boolean.FALSE;
+         }
+      }
+      else if("getActiveThreadsCount".equals(methodName))
+      {
+         return new Integer(activeThreadsCount);
+      }
+
+      // attempt to grab the reader's lock and go forward
+
+      try
+      {
+         boolean acquired = false;
+
+         while(!acquired)
+         {
+            try
+            {
+               acquired = readLock.attempt(500);
+            }
+            catch(InterruptedException e)
+            {
+               // OK
+            }
+
+            if (trace && !acquired ) { log.trace(methodName + "() trying to pass through " + this); }
+         }
+
+         synchronized(this)
+         {
+            activeThreadsCount++;
+            if (trace)
+            {
+               activeMethods.add(methodName);
+            }
+         }
+
+         if(trace) { log.trace(this + " has let " + methodName + "() pass through"); }
+
+         return invocation.invokeNext();
+      }
+      finally
+      {
+         readLock.release();
+         synchronized(this)
+         {
+            activeThreadsCount--;
+            if (trace)
+            {
+               activeMethods.remove(methodName);
+            }
+         }
+      }
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public String toString()
+   {
+      return "FailoverValve." +
+         (delegate == null ? "UNITIALIZED" : delegate.toString()) +
+         (valveOpen ? "[OPEN(" + activeThreadsCount +
+            (trace ? " " + activeMethods.toString() : "") + ")]":"[CLOSED]");
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner classes --------------------------------------------------------------------------------
+}

Deleted: trunk/src/main/org/jboss/jms/client/container/HAAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/HAAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/HAAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -1,700 +0,0 @@
-/*
-   * JBoss, Home of Professional Open Source
-   * Copyright 2005, JBoss Inc., and individual contributors as indicated
-   * by the @authors tag. See the copyright.txt in the distribution for a
-   * full listing of individual contributors.
-   *
-   * This is free software; you can redistribute it and/or modify it
-   * under the terms of the GNU Lesser General Public License as
-   * published by the Free Software Foundation; either version 2.1 of
-   * the License, or (at your option) any later version.
-   *
-   * This software is distributed in the hope that it will be useful,
-   * but WITHOUT ANY WARRANTY; without even the implied warranty of
-   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   * Lesser General Public License for more details.
-   *
-   * You should have received a copy of the GNU Lesser General Public
-   * License along with this software; if not, write to the Free
-   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
-   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-   */
-
-package org.jboss.jms.client.container;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jms.JMSException;
-import javax.jms.Session;
-
-import org.jboss.aop.Advised;
-import org.jboss.aop.advice.Interceptor;
-import org.jboss.aop.joinpoint.Invocation;
-import org.jboss.aop.joinpoint.MethodInvocation;
-import org.jboss.jms.client.delegate.ClientBrowserDelegate;
-import org.jboss.jms.client.delegate.ClientConnectionDelegate;
-import org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate;
-import org.jboss.jms.client.delegate.ClientConsumerDelegate;
-import org.jboss.jms.client.delegate.ClientProducerDelegate;
-import org.jboss.jms.client.delegate.ClientSessionDelegate;
-import org.jboss.jms.client.delegate.ClusteredClientConnectionFactoryDelegate;
-import org.jboss.jms.client.delegate.DelegateSupport;
-import org.jboss.jms.client.remoting.CallbackManager;
-import org.jboss.jms.client.remoting.MessageCallbackHandler;
-import org.jboss.jms.client.state.BrowserState;
-import org.jboss.jms.client.state.ConnectionState;
-import org.jboss.jms.client.state.ConsumerState;
-import org.jboss.jms.client.state.HierarchicalStateSupport;
-import org.jboss.jms.client.state.ProducerState;
-import org.jboss.jms.client.state.SessionState;
-import org.jboss.jms.delegate.SessionDelegate;
-import org.jboss.jms.destination.JBossDestination;
-import org.jboss.jms.server.endpoint.CreateConnectionResult;
-import org.jboss.jms.server.endpoint.DeliveryInfo;
-import org.jboss.jms.server.endpoint.DeliveryRecovery;
-import org.jboss.jms.tx.ResourceManager;
-import org.jboss.jms.tx.ResourceManagerFactory;
-import org.jboss.logging.Logger;
-import org.jboss.remoting.Client;
-import org.jboss.remoting.ConnectionListener;
-
-/**
- *
- * A HAAspect
- *
- * There is one of these PER_INSTANCE of connection factory
- *
- * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
- * @author <a href="mailto:clebert.suconic at jboss.org">Clebert Suconic</a>
- * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
- *
- * @version <tt>$Revision: 1.1 $</tt>
- *
- * $Id$
- */
-public class HAAspect
-{
-   // Constants -----------------------------------------------------
-
-   private static final Logger log = Logger.getLogger(HAAspect.class);
-
-   public static final int MAX_RECONNECT_HOP_COUNT = 10;
-
-   public static final int MAX_IO_RETRY_COUNT = 2;
-
-   // Setting this attribute to false will disable the valve
-   public static final boolean INSTALL_VALVE = true;
-
-   // Static --------------------------------------------------------
-
-   private static boolean trace = log.isTraceEnabled();
-
-   // Attributes ----------------------------------------------------
-
-   private ClientConnectionFactoryDelegate[] delegates;
-
-   private Map failoverMap;
-
-   private int currentRobinIndex;
-
-   // The identity of the delegate this interceptor is associated with
-   private DelegateIdentity id;
-
-   // Constructors --------------------------------------------------
-
-   public HAAspect()
-   {
-      id = null;
-   }
-
-   /** A Copy Constructor Used on creationg of ValveAspect */
-   protected HAAspect(HAAspect copyFrom)
-   {
-      this.delegates = copyFrom.delegates;
-      this.failoverMap = copyFrom.failoverMap;
-      this.id = copyFrom.id;
-   }
-
-   // Public --------------------------------------------------------
-
-   public Object handleCreateConnectionDelegate(Invocation invocation) throws Throwable
-   {
-      // maintain the identity of the delegate that sends invocation through this aspect, for
-      // logging purposes. It makes sense, since it's an PER_INSTANCE aspect.
-      if (id == null)
-      {
-         id = DelegateIdentity.getIdentity(invocation);
-      }
-
-      cacheLocalDelegates(invocation);
-
-      // TODO: I wanted to change aop-messaging-client.xml to only execute handleCreateConnectionDelegate
-      //       on instances of ClusteredClientConnectionFactoryDelegate, but I couldn't find the right
-      //       pointcut expression. So, this is a to do.
-      //      However the following test is enough for now.
-      if (delegates == null)
-      {
-         // not clustered, pass the invocation through
-         if(trace) { log.trace(this + " detecting non-clustered connection creation request, letting it pass through"); }
-
-         return invocation.invokeNext();
-      }
-
-      // clustered, stopping the invocation here and re-send it as non-clustered down the stack
-
-      // TODO: this should be in loop while we get exceptions creating connections, always trying
-      //       the next Delegate when we get an exception.
-
-      // In a clustered configuration we create connections in a round-robin fashion, contacting
-      // successively all available servers.
-
-      ClientConnectionFactoryDelegate cfDelegate = getDelegateRoundRobin();
-
-      if(trace) { log.trace(this + " detecting clustered connection creation request, choosing " + cfDelegate + " as target"); }
-
-      // Now create a connection delegate for this
-
-      MethodInvocation mi = (MethodInvocation)invocation;
-      String username = (String)mi.getArguments()[0];
-      String password = (String)mi.getArguments()[1];
-
-      CreateConnectionResult res = (CreateConnectionResult)cfDelegate.
-         createConnectionDelegate(username, password, -1);
-
-      ClientConnectionDelegate cd = (ClientConnectionDelegate)res.getDelegate();
-
-      // TODO: The valve is optional for now
-      if (INSTALL_VALVE)
-      {
-         installValveAspect(cd, new ValveAspect(cd, this));
-      }
-      else
-      {
-         ((ConnectionState) ((DelegateSupport) cd).getState()).
-            getRemotingConnectionListener().addDelegateListener(new ConnectionFailureListener(cd));
-      }
-
-      if(trace) { log.trace(this + " got local connection delegate " + cd); }
-
-      // Add a connection listener to detect failure; the consolidated remoting connection listener
-      // must be already in place and configured
-
-      return new CreateConnectionResult(cd);
-   }
-
-   public String toString()
-   {
-      StringBuffer sb = new StringBuffer("HAAspect.");
-      if (id == null)
-      {
-         sb.append("UNINITIALIZED");
-      }
-      else
-      {
-         sb.append(id.getType()).append("[").append(id.getID()).append("]");
-      }
-      return sb.toString();
-   }
-
-   // Package protected ---------------------------------------------
-
-   // Protected -----------------------------------------------------
-
-   // Private -------------------------------------------------------
-
-   private synchronized void cacheLocalDelegates(Invocation invocation)
-   {
-      if (delegates != null)
-      {
-         // the first set of delegates is already cached, return
-         return;
-      }
-
-      MethodInvocation mi = (MethodInvocation)invocation;
-      Object target = mi.getTargetObject();
-
-      if (target instanceof ClusteredClientConnectionFactoryDelegate)
-      {
-         ClusteredClientConnectionFactoryDelegate cccfd =
-            (ClusteredClientConnectionFactoryDelegate)target;
-
-         delegates = cccfd.getDelegates();
-
-         if (delegates != null)
-         {
-            failoverMap = cccfd.getFailoverMap();
-
-            if (failoverMap == null)
-            {
-               throw new IllegalStateException("HAAspect cannot find the failover map!");
-            }
-         }
-      }
-   }
-
-   /** The valve aspect needs to stay after ExceptionInterceptor, and before DelegateSupport.
-    *  This method will place the aspect on the proper place */
-   protected void installValveAspect(DelegateSupport delegate, Interceptor interceptor)
-   {
-      Advised advised = (Advised)delegate;
-      Interceptor interceptors[] = advised._getInstanceAdvisor().getInterceptors();
-
-      //printInterceptors(interceptors);
-
-      Interceptor delegateInterceptorFound = null;
-
-      for (int i=0;i<interceptors.length;i++)
-      {
-         if (interceptors[i] instanceof DelegateSupport)
-         {
-            delegateInterceptorFound = interceptors[i];
-         }
-      }
-
-
-      if (delegateInterceptorFound!=null)
-      {
-         advised._getInstanceAdvisor().removeInterceptor(delegateInterceptorFound.getName());
-      }
-
-      advised._getInstanceAdvisor().appendInterceptor(interceptor);
-
-      if (delegateInterceptorFound!=null)
-      {
-         advised._getInstanceAdvisor().appendInterceptor(delegateInterceptorFound);
-      }
-
-      //printInterceptors(advised._getInstanceAdvisor().getInterceptors());
-
-   }
-   
-
-   //TODO this is currently hardcoded as round-robin, this should be made pluggable
-   private synchronized ClientConnectionFactoryDelegate getDelegateRoundRobin()
-   {
-      ClientConnectionFactoryDelegate currentDelegate = delegates[currentRobinIndex++];
-
-      if (currentRobinIndex >= delegates.length)
-      {
-         currentRobinIndex = 0;
-      }
-
-      return currentDelegate;
-   }
-
-   /**
-    * @return a failover ClientConnectionFactoryDelegate or null if a suitable delegate cannot be
-    *         found.
-    */
-   private ClientConnectionFactoryDelegate getFailoverDelegate(int failedServerID)
-   {
-      // Look up the server to failover onto in the failover map
-
-      Integer failoverServerID = (Integer)failoverMap.get(new Integer(failedServerID));
-
-      for (int i = 0; i < delegates.length; i++)
-      {
-         if (delegates[i].getServerID() == failoverServerID.intValue())
-         {
-            return delegates[i];
-         }
-      }
-
-      return null;
-   }
-
-   protected void handleConnectionFailure(ClientConnectionDelegate failedConnDelegate)
-      throws Exception
-   {
-      log.debug(this + " handling failed connection " + failedConnDelegate);
-
-      ConnectionState failedConnState =
-         (ConnectionState)((DelegateSupport)failedConnDelegate).getState();
-
-      int failedServerID = failedConnState.getServerID();
-
-      // Get the default connection factory delegate we are going to failover onto
-
-      ClientConnectionFactoryDelegate failoverDelegate = getFailoverDelegate(failedServerID);
-
-      if (failoverDelegate == null)
-      {
-         throw new IllegalStateException("Cannot find default failover node for server " +
-                                         failedServerID);
-      }
-
-      // We attempt to connect to the failover node in a loop, since we might need to go through
-      // multiple hops
-
-      int attemptCount = 0;
-
-      outer: while (attemptCount < MAX_RECONNECT_HOP_COUNT)
-      {
-         // Create a connection using that connection factory
-         CreateConnectionResult r = failoverDelegate.
-            createConnectionDelegate(failedConnState.getUser(),
-                                     failedConnState.getPassword(),
-                                     failedConnState.getServerID());
-
-         if (r.getDelegate() != null)
-         {
-            // We got the right server and created a new connection
-            performClientSideFailover(failedConnDelegate, (ClientConnectionDelegate)r.getDelegate());
-            return;
-         }
-
-         // Did not get a valid connection to the node we've just tried
-
-         int actualServerID = r.getActualFailoverNodeID();
-
-         if (actualServerID == -1)
-         {
-            // No failover attempt was detected on the server side; this might happen if the client
-            // side network fails temporarily so the client connection breaks but the server cluster
-            // is still up and running - in this case we don't perform failover.
-
-            //TODO Is this the right thing to do?
-
-            log.warn("Client attempted failover, but no failover attempt " +
-                     "has been detected on the server side.");
-
-            return;
-         }
-
-         // Server side failover has occurred / is occurring but trying to go to the 'default'
-         // failover node did not succeed. Retry with the node suggested by the cluster.
-
-         attemptCount++;
-
-         for (int i = 0; i < delegates.length; i++)
-         {
-            if (delegates[i].getServerID() == actualServerID)
-            {
-               failoverDelegate = delegates[i];
-               continue outer;
-            }
-         }
-
-         // the delegate corresponding to the actualServerID not found among the cached delegates
-         //TODO Could this ever happen? Should we send back the cf, or update it instead of just the id??
-         throw new JMSException("Cannot find a cached connection factory delegate for " +
-                                "node " + actualServerID);
-      }
-
-      throw new JMSException("Maximum number of failover attempts exceeded. " +
-                             "Cannot find a server to failover onto.");
-   }
-
-   protected void performClientSideFailover(ClientConnectionDelegate failedConnDelegate,
-                                            ClientConnectionDelegate newConnDelegate)
-      throws Exception
-   {
-      log.debug(this + " performing client side failover");
-
-      ConnectionState failedState = (ConnectionState)failedConnDelegate.getState();
-      ConnectionState newState = (ConnectionState)newConnDelegate.getState();
-
-      if (failedState.getClientID() != null)
-      {
-         newConnDelegate.setClientID(failedState.getClientID());
-      }
-
-      // Transfer attributes from newDelegate to failedDelegate
-      failedConnDelegate.copyAttributes(newConnDelegate);
-
-      CallbackManager oldCallbackManager = failedState.getRemotingConnection().getCallbackManager();
-
-      // We need to update some of the attributes on the state
-      failedState.copyState(newState);
-      
-      int failedServerId = failedConnDelegate.getServerID();
-      
-      int newServerId = newConnDelegate.getServerID();
-      
-      if (trace) { log.trace("Failing over from " + failedServerId + " to " + newServerId); }
-      
-      // There is one RM per server, so we need to merge the rms if necessary
-      ResourceManagerFactory.instance.handleFailover(failedServerId, newServerId);
-      
-      ResourceManager rm = failedState.getResourceManager();
-
-      for(Iterator i = failedState.getChildren().iterator(); i.hasNext(); )
-      {
-         SessionState failedSessionState = (SessionState)i.next();
-         
-         int oldSessionId = failedSessionState.getSessionId();
-
-         ClientSessionDelegate failedSessionDelegate =
-            (ClientSessionDelegate)failedSessionState.getDelegate();
-
-         ClientSessionDelegate newSessionDelegate = (ClientSessionDelegate)newConnDelegate.
-            createSessionDelegate(failedSessionState.isTransacted(),
-                                  failedSessionState.getAcknowledgeMode(),
-                                  failedSessionState.isXA());
-
-         SessionState newSessionState = (SessionState)newSessionDelegate.getState();
-         
-         int newSessionId = newSessionState.getSessionId();
-
-         if (trace) { log.trace("new session state has " + newSessionState.getClientAckList().size() + " deliveries"); }
-
-         failedSessionDelegate.copyAttributes(newSessionDelegate);
-
-         // We need to update some of the attributes on the state
-         failedSessionState.copyState(newSessionState);
-
-         for (Iterator j = failedSessionState.getChildren().iterator(); j.hasNext(); )
-         {
-            HierarchicalStateSupport sessionChild = (HierarchicalStateSupport)j.next();
-
-            if (sessionChild instanceof ProducerState)
-            {
-               handleFailoverOnProducer((ProducerState)sessionChild, newSessionDelegate);
-            }
-            else if (sessionChild instanceof ConsumerState)
-            {
-               handleFailoverOnConsumer(failedConnDelegate, (ConsumerState)sessionChild,
-                                         newSessionDelegate, oldCallbackManager);
-            }
-            else if (sessionChild instanceof BrowserState)
-            {
-                handleFailoverOnBrowser((BrowserState)sessionChild, newSessionDelegate);
-            }
-         }
-         
-         // We need to failover from one session id to another in the resource manager
-         rm.handleFailover(newServerId, oldSessionId, newSessionId);
-               
-         List ackInfos = Collections.EMPTY_LIST;
-
-         if (!failedSessionState.isTransacted() ||
-             (failedSessionState.isXA() && failedSessionState.getCurrentTxId() == null))
-         {
-            // Non transacted session or an XA session with no transaction set (it falls back
-            // to auto_ack)
-
-            if (trace) { log.trace(failedSessionState + " is not transacted (or XA with no tx set), retrieving deliveries from session state"); }
-
-            // We remove any unacked non-persistent messages - this is because we don't want to ack
-            // them since the server won't know about them and will get confused
-
-            if (failedSessionState.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
-            {
-               for(Iterator j = failedSessionState.getClientAckList().iterator(); j.hasNext(); )
-               {
-                  DeliveryInfo info = (DeliveryInfo)j.next();
-                  if (!info.getMessageProxy().getMessage().isReliable())
-                  {
-                     j.remove();
-                     if (trace) { log.trace("removed non persistent delivery " + info); }
-                  }
-               }
-
-               ackInfos = failedSessionState.getClientAckList();
-            }
-            else
-            {
-               DeliveryInfo autoAck = failedSessionState.getAutoAckInfo();
-               if (autoAck != null)
-               {
-                  if (!autoAck.getMessageProxy().getMessage().isReliable())
-                  {
-                     // unreliable, discard
-                     failedSessionState.setAutoAckInfo(null);
-                  }
-                  else
-                  {
-                     // reliable
-                     ackInfos = new ArrayList();
-                     ackInfos.add(autoAck);
-                  }
-               }
-            }
-
-            if (trace) { log.trace(this + " retrieved " + ackInfos.size() + " deliveries"); }
-         }
-         else
-         {
-            // Transacted session - we need to get the acks from the resource manager. BTW we have
-            // kept the old resource manager
-
-            ackInfos = rm.getDeliveriesForSession(failedSessionState.getSessionId());
-         }
-
-         if (!ackInfos.isEmpty())
-         {
-            SessionDelegate newDelegate = (SessionDelegate)failedSessionState.getDelegate();
-
-            List recoveryInfos = new ArrayList();
-
-            for (Iterator iter2 = ackInfos.iterator(); iter2.hasNext(); )
-            {
-               DeliveryInfo info = (DeliveryInfo)iter2.next();
-
-               DeliveryRecovery recInfo =
-                  new DeliveryRecovery(info.getMessageProxy().getDeliveryId(),
-                                       info.getMessageProxy().getMessage().getMessageID(),
-                                       info.getChannelId());
-
-               recoveryInfos.add(recInfo);
-            }
-
-            if (trace) { log.trace(this + " sending delivery recovery info: " + recoveryInfos); }
-            newDelegate.recoverDeliveries(recoveryInfos);
-         }
-         else
-         {
-            if (trace) { log.trace(this + " no delivery recovery info to send"); }
-         }
-      }
-
-      // We must not start the connection until the end
-      if (failedState.isStarted())
-      {
-         failedConnDelegate.start();
-      }
-
-      log.info(this + " completed client-side failover");
-   }
-
-   private void handleFailoverOnConsumer(ClientConnectionDelegate failedConnectionDelegate,
-                                         ConsumerState failedConsumerState,
-                                         ClientSessionDelegate newSessionDelegate,
-                                         CallbackManager oldCallbackManager)
-      throws JMSException
-   {
-      log.debug(this + " failing over consumer " + failedConsumerState);
-
-      ClientConsumerDelegate failedConsumerDelegate =
-         (ClientConsumerDelegate)failedConsumerState.getDelegate();
-
-      if (trace) { log.trace(this + " creating alternate consumer"); }
-
-      ClientConsumerDelegate newConsumerDelegate = (ClientConsumerDelegate)newSessionDelegate.
-         createConsumerDelegate((JBossDestination)failedConsumerState.getDestination(),
-                                 failedConsumerState.getSelector(),
-                                 failedConsumerState.isNoLocal(),
-                                 failedConsumerState.getSubscriptionName(),
-                                 failedConsumerState.isConnectionConsumer(),
-                                 failedConsumerState.getChannelId());
-
-      if (trace) { log.trace(this + " alternate consumer created"); }
-
-      // Copy the attributes from the new consumer to the old consumer
-      failedConsumerDelegate.copyAttributes(newConsumerDelegate);
-
-      ConsumerState newState = (ConsumerState)newConsumerDelegate.getState();
-
-      int oldConsumerID = failedConsumerState.getConsumerID();
-
-      // Update attributes on the old state
-      failedConsumerState.copyState(newState);
-
-      // We need to re-use the existing message callback handler
-
-      MessageCallbackHandler oldHandler =
-         oldCallbackManager.unregisterHandler(oldConsumerID);
-
-      ConnectionState newConnectionState = (ConnectionState)failedConnectionDelegate.getState();
-
-      CallbackManager newCallbackManager =
-         newConnectionState.getRemotingConnection().getCallbackManager();
-
-      // Remove the new handler
-      MessageCallbackHandler newHandler = newCallbackManager.
-         unregisterHandler(newState.getConsumerID());
-
-      log.debug("New handler is " + System.identityHashCode(newHandler));
-
-      //But we need to update some fields from the new one
-      oldHandler.copyState(newHandler);
-
-      //Now we re-register the old handler with the new callback manager
-
-      newCallbackManager.registerHandler(newState.getConsumerID(),
-                                         oldHandler);
-
-      // We don't need to add the handler to the session state since it is already there - we
-      // are re-using the old handler
-
-      log.debug(this + " failed over consumer");
-   }
-
-
-   private void handleFailoverOnProducer(ProducerState failedProducerState,
-                                         ClientSessionDelegate failedSessionDelegate)
-      throws JMSException
-   {
-      ClientProducerDelegate newProducerDelegate = (ClientProducerDelegate)failedSessionDelegate.
-         createProducerDelegate((JBossDestination)failedProducerState.getDestination());
-
-      ClientProducerDelegate failedProducerDelegate =
-         (ClientProducerDelegate)failedProducerState.getDelegate();
-
-      failedProducerDelegate.copyAttributes(newProducerDelegate);
-      failedProducerState.copyState((ProducerState)newProducerDelegate.getState());
-
-      if (trace) { log.trace("handling fail over on producerDelegate " + failedProducerDelegate + " destination=" + failedProducerState.getDestination()); }
-   }
-
-   private void handleFailoverOnBrowser(BrowserState failedBrowserState,
-                                         ClientSessionDelegate failedSessionDelegate)
-      throws JMSException
-   {
-      ClientBrowserDelegate newBrowserDelegate = (ClientBrowserDelegate)failedSessionDelegate.
-         createBrowserDelegate(failedBrowserState.getJmsDestination(),
-                               failedBrowserState.getMessageSelector());
-
-      ClientBrowserDelegate failedBrowserDelegate =
-         (ClientBrowserDelegate)failedBrowserState.getDelegate();
-
-      failedBrowserDelegate.copyAttributes(newBrowserDelegate);
-      failedBrowserState.copyState((BrowserState)newBrowserDelegate.getState());
-
-      if (trace) { log.trace("handling fail over on browserDelegate " + failedBrowserDelegate + " destination=" + failedBrowserState.getJmsDestination() + " selector=" + failedBrowserState.getMessageSelector()); }
-
-   }
-
-   /** I have moved this ConnectionListener to ValveAspect (from HAAspect) because
-    *  it needs to use the same valve as exception listeners.
-    *  While we are processing failover, we should block any calls on the client side.
-    *  (No call should be made while the client failover is being executed). It doesn't matter if
-    *  the failover was captured by Lease (ConnectionFactory) or Exception handling on invoke at this class */
-   private class ConnectionFailureListener implements ConnectionListener
-   {
-      private ClientConnectionDelegate cd;
-
-      ConnectionFailureListener(ClientConnectionDelegate cd)
-      {
-         this.cd = cd;
-      }
-
-      // ConnectionListener implementation ---------------------------
-
-      public void handleConnectionException(Throwable throwable, Client client)
-      {
-         try
-         {
-            log.debug(this + " is being notified of connection failure: " + throwable);
-            handleConnectionFailure(cd);
-         }
-         catch (Throwable e)
-         {
-            log.error("Caught exception in handling failure", e);
-         }
-      }
-
-      public String toString()
-      {
-         return "ConnectionFailureListener[" + cd + "]";
-      }
-   }
-
-   
-}
-
-

Modified: trunk/src/main/org/jboss/jms/client/container/SessionAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/SessionAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/SessionAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -79,7 +79,8 @@
       SessionState state = getState(invocation);
       SessionDelegate del = (SessionDelegate)mi.getTargetObject();
             
-      if (trace) { log.trace("In handleClosing"); }
+      if (trace) { log.trace("handleClosing()"); }
+
       //Sanity check
       if (state.isXA())
       {
@@ -204,25 +205,25 @@
       {
          // We collect acknowledgments in the list
          
-         if (trace) { log.trace(this + " delivery id: " + info.getDeliveryId() + " added to client ack list"); }
+         if (trace) { log.trace(this + " added to CLIENT_ACKNOWLEDGE list delivery " + info); }
          
-         //Sanity check
+         // Sanity check
          if (info.getConnectionConsumerSession() != null)
          {
             throw new IllegalStateException("CLIENT_ACKNOWLEDGE cannot be used with a connection consumer");
          }
                   
-         state.getClientAckList().add(info);         
+         state.getClientAckList().add(info);
       }
       else if (ackMode == Session.AUTO_ACKNOWLEDGE ||
                ackMode == Session.DUPS_OK_ACKNOWLEDGE ||
                (state.isXA() && state.getCurrentTxId() == null))
       {
-         //We collect the single acknowledgement in the state.
-         //Currently DUPS_OK is treated the same as AUTO_ACKNOWLDGE
-         //Also XA sessions not enlisted in a global tx are treated as AUTO_ACKNOWLEDGE
+         // We collect the single acknowledgement in the state. Currently DUPS_OK is treated the
+         // same as AUTO_ACKNOWLDGE. Also XA sessions not enlisted in a global tx are treated as
+         // AUTO_ACKNOWLEDGE.
                            
-         if (trace) { log.trace(this + " delivery id: " + info.getDeliveryId() + " added to client ack member"); }
+         if (trace) { log.trace(this + " added " + info + " to session state"); }
          
          state.setAutoAckInfo(info);         
       }
@@ -280,22 +281,22 @@
          //will be redelivered
          if (!state.isRecoverCalled())
          {
-            DeliveryInfo deliveryInfo = state.getAutoAckInfo();
+            DeliveryInfo delivery = state.getAutoAckInfo();
             
-            if (deliveryInfo == null)
+            if (delivery == null)
             {
-               throw new IllegalStateException("Cannot find delivery to auto ack");
+               throw new IllegalStateException("Cannot find delivery to AUTO_ACKNOWLEDGE");
             }
                                  
-            if (trace) { log.trace(this + " auto acking delivery " + deliveryInfo.getDeliveryId()); }
+            if (trace) { log.trace(this + " auto acknowledging delivery " + delivery); }
                         
-            ackDelivery(sd, deliveryInfo);            
-            
+            ackDelivery(sd, delivery);
+
             state.setAutoAckInfo(null);
          }
          else
          {
-            if (trace) { log.trace("recover called, so NOT acknowledging"); }
+            if (trace) { log.trace(this + " recover called, so NOT acknowledging"); }
 
             state.setRecoverCalled(false);
          }
@@ -553,6 +554,12 @@
    {
       return new Integer(getState(invocation).getAcknowledgeMode());
    }
+
+   public String toString()
+   {
+      return "SessionAspect[" + Integer.toHexString(hashCode()) + "]";
+   }
+
    
 
    // Class YYY overrides -------------------------------------------

Modified: trunk/src/main/org/jboss/jms/client/container/StateCreationAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/StateCreationAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/StateCreationAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -79,8 +79,6 @@
 
    // Public --------------------------------------------------------
 
-   // Interceptor implementation -----------------------------------
-
    public Object handleCreateConnectionDelegate(Invocation inv) throws Throwable
    {
       ConnectionFactoryDelegate cfd = (ConnectionFactoryDelegate)inv.getTargetObject();
@@ -117,14 +115,10 @@
             MessageIdGeneratorFactory.instance.checkOutGenerator(serverID, cfd);
 
          ConnectionState connectionState =
-            new ConnectionState(serverID, connectionDelegate, remotingConnection,
-                                listener, versionToUse, idGenerator);
+            new ConnectionState(serverID, connectionDelegate,
+                                remotingConnection, listener, versionToUse, idGenerator);
 
          connectionDelegate.setState(connectionState);
-
-         // the delegate is completely configured now; will use this state to avoid redundant
-         // configuration on a double pass through StateCreationAspect, which happens for clustered
-         // connections
       }
 
       return res;

Deleted: trunk/src/main/org/jboss/jms/client/container/ValveAspect.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/container/ValveAspect.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/container/ValveAspect.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -1,256 +0,0 @@
-/*
-   * JBoss, Home of Professional Open Source
-   * Copyright 2005, JBoss Inc., and individual contributors as indicated
-   * by the @authors tag. See the copyright.txt in the distribution for a
-   * full listing of individual contributors.
-   *
-   * This is free software; you can redistribute it and/or modify it
-   * under the terms of the GNU Lesser General Public License as
-   * published by the Free Software Foundation; either version 2.1 of
-   * the License, or (at your option) any later version.
-   *
-   * This software is distributed in the hope that it will be useful,
-   * but WITHOUT ANY WARRANTY; without even the implied warranty of
-   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   * Lesser General Public License for more details.
-   *
-   * You should have received a copy of the GNU Lesser General Public
-   * License along with this software; if not, write to the Free
-   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
-   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-   */
-
-package org.jboss.jms.client.container;
-
-import org.jboss.aop.advice.Interceptor;
-import org.jboss.aop.joinpoint.Invocation;
-import org.jboss.jms.client.delegate.DelegateSupport;
-import org.jboss.jms.client.delegate.ClientConnectionDelegate;
-import org.jboss.jms.client.state.ConnectionState;
-import org.jboss.jms.client.remoting.JMSRemotingConnection;
-import org.jboss.logging.Logger;
-import org.jboss.remoting.CannotConnectException;
-import org.jboss.remoting.ConnectionListener;
-import org.jboss.remoting.Client;
-import java.io.IOException;
-import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
-import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
-
-/**
- * This aspect will intercept failures from any HA object.
- * <p/>
- * The reason why I've made an extension of HAAspect instead of implementing new methods there is
- * ValveAspect needs to cache ClientConnectionDelegate while HAAspect needs to cache CF related objects.
- * I have made this an extension of HAAspect as it's needed one instance of this aspect per
- * ConnectionCreated.
- * <p/>
- * We will cache the ClientConnectionDelegate on this aspect so we won't need to do any operation on delegates
- * to retrieve the current ConnectionDelegate.
- *
- * @author <a href="mailto:clebert.suconic at jboss.org">Clebert Suconic</a>
- * @version <tt>$Revision:$</tt>
- *          <p/>
- *          $Id:$
- */
-public class ValveAspect extends HAAspect implements Interceptor
-{
-   private static final Logger log = Logger.getLogger(ValveAspect.class);
-
-   private ClientConnectionDelegate delegate;
-
-   private ReadWriteLock lockValve;
-
-   ValveAspect(ClientConnectionDelegate delegate, HAAspect copy)
-   {
-      super(copy);
-      this.delegate = delegate;
-      lockValve = new ReentrantWriterPreferenceReadWriteLock();
-
-      ConnectionListener listener = new ConnectionFailureListener(delegate);
-
-      ((ConnectionState) ((DelegateSupport) delegate).getState()).
-         getRemotingConnectionListener().addDelegateListener(listener);
-
-
-   }
-
-   public String getName()
-   {
-      return this.getClass().getName();
-   }
-
-
-   /**
-    * This method executes the valve, listening for erros on the underlaying IO layer,
-    * and it will call the failure for HA
-    */
-   public Object invoke(Invocation invocation) throws Throwable
-   {
-
-      Object returnObject = null;
-
-      boolean failure = false;
-
-      // Eventually retries in case of listed exceptions
-      for (int invocationCount = 0; invocationCount < MAX_IO_RETRY_COUNT; invocationCount++)
-      {
-
-         if (invocationCount > 0)
-         {
-            log.info("Retrying a call " + invocationCount);
-         }
-
-         // We shouldn't have any calls being made while the failover is being executed
-         if (invocationCount > 0)
-         {
-            // On retries we will use writeLocks, as the failover was already being processed
-            lockValve.writeLock().acquire();
-         }
-         else
-         {
-            lockValve.readLock().acquire();
-         }
-
-         failure = false;
-
-         try
-         {
-            returnObject = invocation.invokeNext();
-
-            if (returnObject instanceof DelegateSupport)
-            {
-               installValveAspect((DelegateSupport) returnObject, this);
-            }
-         }
-         catch (CannotConnectException e)
-         {
-            log.error("Got an exception on HAAspect, retryCount=" + invocationCount, e);
-            failure = true;
-         }
-         catch (IOException e)
-         {
-            log.error("Got an exception on HAAspect, retryCount=" + invocationCount, e);
-            failure = true;
-         }
-         catch (Throwable e)
-         {
-            // just rethrow
-            throw e;
-         }
-         finally
-         {
-            if (invocationCount > 0)
-            {
-               lockValve.writeLock().release();
-            }
-            else
-            {
-               lockValve.readLock().release();
-            }
-         }
-
-         if (!failure)
-         {
-            break;
-         }
-      }
-
-
-      if (failure)
-      {
-         handleConnectionFailure(delegate, delegate.getRemotingConnection());
-         // if on the end we still have an exception there is nothing we can do besides throw an exception
-         // so, no retires on the failedOver Invocation
-         returnObject = invocation.invokeNext();
-      }
-
-      // if the object returned is another DelegateSupport, we will install the aspect on the returned object
-
-      return returnObject;
-
-   }
-
-
-   /**
-    * Since we are listening for exceptions on the invocation layer, several objects might
-    * get the exception at the same time.
-    * Suppose you have 30 (or any X number>=2) Consumers, using the same JBossConnection failing at the same time.
-    * We will get simultaneous calls on handleFailures while we just need to process one single failure.
-    * <p/>
-    * On this case this method will open a valve and it will perform only the first handleFailure captured, and
-    * it will just return all the others as soon as the valve is closed. This way all the simultaneous failures will
-    * act as they were processed while we called failover only once.
-    */
-   protected void handleConnectionFailure(ClientConnectionDelegate failedConnDelegate,
-                                          JMSRemotingConnection jmsConnection) throws Exception
-   {
-      log.info("Processing handleConnectionFailure ");
-
-      // only one execution should be performed if multiple exceptions happened at the same time
-      lockValve.writeLock().acquire();
-
-      try
-      {
-         if (jmsConnection.isFailed())
-         {
-            log.info("Failover on " + failedConnDelegate + " was already performed, so just ignoring call to handleConnectionFailure");
-            return;
-         }
-
-         log.info("Processing valve on exception failure");
-         jmsConnection.setFailed(true);
-         super.handleConnectionFailure(failedConnDelegate);
-         log.info("Failover on client finished");
-      }
-      catch (Exception e)
-      {
-         log.error("An exception happened during client failover processing!", e);
-         throw e;
-      }
-      finally
-      {
-         lockValve.writeLock().release();
-      }
-
-   }
-
-   // Inner classes -------------------------------------------------
-
-
-   /** I have moved this ConnectionListener to ValveAspect (from HAAspect) because
-    *  it needs to use the same valve as exception listeners.
-    *  While we are processing failover, we should block any calls on the client side.
-    *  (No call should be made while the client failover is being executed). It doesn't matter if
-    *  the failover was captured by Lease (ConnectionFactory) or Exception handling on invoke at this class */
-   private class ConnectionFailureListener implements ConnectionListener
-   {
-      private ClientConnectionDelegate cd;
-
-      ConnectionFailureListener(ClientConnectionDelegate cd)
-      {
-         this.cd = cd;
-      }
-
-      // ConnectionListener implementation ---------------------------
-
-      public void handleConnectionException(Throwable throwable, Client client)
-      {
-         try
-         {
-            log.debug(this + " is being notified of connection failure: " + throwable);
-            handleConnectionFailure(cd, cd.getRemotingConnection());
-         }
-         catch (Throwable e)
-         {
-            log.error("Caught exception in handling failure", e);
-         }
-      }
-
-      public String toString()
-      {
-         return "ConnectionFailureListener[" + cd + "]";
-      }
-   }
-
-
-}

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientBrowserDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientBrowserDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientBrowserDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -57,7 +57,6 @@
    
    public ClientBrowserDelegate()
    {     
-      
    }
 
    // BrowserDelegate implementation --------------------------------
@@ -96,6 +95,42 @@
       throw new IllegalStateException("This invocation should not be handled here!");
    }
 
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void closeValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void openValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public boolean isValveOpen()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public int getActiveThreadsCount()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
    // Public --------------------------------------------------------
 
    public String getStackName()

Copied: trunk/src/main/org/jboss/jms/client/delegate/ClientClusteredConnectionFactoryDelegate.java (from rev 1870, trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java)
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java	2006-12-29 03:02:10 UTC (rev 1870)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientClusteredConnectionFactoryDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,172 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.jms.client.delegate;
+
+import java.util.Map;
+import java.io.Serializable;
+
+import org.jboss.jms.server.endpoint.CreateConnectionResult;
+import org.jboss.jms.delegate.ConnectionFactoryDelegate;
+import org.jboss.messaging.core.plugin.IDBlock;
+import org.jboss.logging.Logger;
+
+import javax.jms.JMSException;
+
+/**
+ * A ClientClusteredConnectionFactoryDelegate.
+ *
+ * It DOESN'T extend DelegateSupport, because none of DelegateSupport's attributes make sense here:
+ * there is no corresponding enpoint on the server, there's no ID, etc. This is just a "shallow"
+ * delegate, that in turn delegates to its sub-delegates (ClientConnectionFactoryDelegate instances)
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @author <a href="mailto:clebert.suconic at jboss.org">Clebert Suconic</a>
+ * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
+ *
+ * @version <tt>$Revision: 1.1 $</tt>
+ *
+ * $Id$
+ */
+public class ClientClusteredConnectionFactoryDelegate
+   implements Serializable, Initializable, ConnectionFactoryDelegate
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   private static final long serialVersionUID = 8286850860206289277L;
+
+   private static final Logger log =
+      Logger.getLogger(ClientClusteredConnectionFactoryDelegate.class);
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private ClientConnectionFactoryDelegate[] delegates;
+
+   // Map <Integer(nodeID)->Integer(failoverNodeID)>
+   private Map failoverMap;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   public ClientClusteredConnectionFactoryDelegate(ClientConnectionFactoryDelegate[] delegates,
+                                                   Map failoverMap)
+   {
+      this.delegates = delegates;
+      this.failoverMap = failoverMap;
+   }
+
+   // ConnectionFactoryDelegate implementation -----------------------------------------------------
+
+   public byte[] getClientAOPStack()
+   {
+      // Use one of the non-clustered ConnectionFactory delegates to retrieve the client AOP stack
+      // from one of the nodes.
+
+      ConnectionFactoryDelegate aopStackProvider = delegates[0];
+
+      log.debug("getting AOP stack from " + aopStackProvider);
+
+      return aopStackProvider.getClientAOPStack();
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public CreateConnectionResult createConnectionDelegate(String username, String password,
+                                                          int failedNodeID) throws JMSException
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public IDBlock getIdBlock(int size) throws JMSException
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   // Initializable implemenation ------------------------------------------------------------------
+
+   public void init()
+   {
+      for (int i = 0; i < delegates.length; i++)
+      {
+         delegates[i].init();
+      }
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public ClientConnectionFactoryDelegate[] getDelegates()
+   {
+      return delegates;
+   }
+
+   public void setDelegates(ClientConnectionFactoryDelegate[] dels)
+   {
+      this.delegates = dels;
+   }
+
+   public Map getFailoverMap()
+   {
+      return failoverMap;
+   }
+
+   public void setFailoverMap(Map failoverMap)
+   {
+      this.failoverMap = failoverMap;
+   }
+
+   public String toString()
+   {
+      StringBuffer sb = new StringBuffer("ClusteredConnectionFactoryDelegate[");
+      if (delegates == null)
+      {
+         sb.append("-]");
+      }
+      else
+      {
+         for(int i = 0; i < delegates.length; i++)
+         {
+            sb.append(delegates[i].getServerID());
+            if (i < delegates.length - 1)
+            {
+               sb.append(',');
+            }
+         }
+         sb.append("]");
+      }
+      return sb.toString();
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner classes --------------------------------------------------------------------------------
+
+}

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -29,12 +29,14 @@
 import javax.transaction.xa.Xid;
 
 import org.jboss.jms.client.JBossConnectionConsumer;
+import org.jboss.jms.client.FailoverListener;
 import org.jboss.jms.client.remoting.JMSRemotingConnection;
 import org.jboss.jms.client.state.ConnectionState;
 import org.jboss.jms.delegate.ConnectionDelegate;
 import org.jboss.jms.delegate.SessionDelegate;
 import org.jboss.jms.server.Version;
 import org.jboss.jms.tx.TransactionRequest;
+import org.jboss.jms.tx.ResourceManagerFactory;
 import org.jboss.remoting.Client;
 
 /**
@@ -50,35 +52,72 @@
  */
 public class ClientConnectionDelegate extends DelegateSupport implements ConnectionDelegate
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
    private static final long serialVersionUID = 6680015509555859038L;
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
    private int serverID;
-
    private transient JMSRemotingConnection remotingConnection;
-   
    private Version versionToUse;
    
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
-   public ClientConnectionDelegate(int objectID, int serverId)
+   public ClientConnectionDelegate(int objectID, int serverID)
    {
       super(objectID);
-      
-      this.serverID = serverId;
+      this.serverID = serverID;
    }
 
    public ClientConnectionDelegate()
    {
    }
 
-   // ConnectionDelegate implementation -----------------------------
+   // DelegateSupport overrides --------------------------------------------------------------------
 
+   public void synchronizeWith(DelegateSupport nd) throws Exception
+   {
+      super.synchronizeWith(nd);
+
+      ClientConnectionDelegate newDelegate = (ClientConnectionDelegate)nd;
+
+      // synchronize the server endpoint state
+
+      // this is a bit counterintuitve, as we're not copying from new delegate, but modifying its
+      // state based on the old state. It makes sense, since in the end the state makes it to the
+      // server
+
+      ConnectionState thisState = (ConnectionState)state;
+
+      if (thisState.getClientID() != null)
+      {
+         newDelegate.setClientID(thisState.getClientID());
+      }
+
+      // synchronize the delegates
+
+      remotingConnection = newDelegate.getRemotingConnection();
+      versionToUse = newDelegate.getVersionToUse();
+
+      // There is one RM per server, so we need to merge the rms if necessary
+      ResourceManagerFactory.instance.handleFailover(serverID, newDelegate.getServerID());
+
+      // synchronize (recursively) the client-side state
+
+      state.synchronizeWith(newDelegate.getState());
+
+      // start the new connection if necessary
+      if (thisState.isStarted())
+      {
+         newDelegate.start();
+      }
+   }
+
+   // ConnectionDelegate implementation ------------------------------------------------------------
+
    /**
     * This invocation should either be handled by the client-side interceptor chain or by the
     * server-side endpoint.
@@ -213,13 +252,68 @@
       throw new IllegalStateException("This invocation should not be handled here!");
    }
 
-   // Public --------------------------------------------------------
+   /**
+    * This invocation should be handled by the client-side interceptor chain.
+    */
+   public void registerFailoverListener(FailoverListener l)
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
 
-   public String toString()
+   /**
+    * This invocation should be handled by the client-side interceptor chain.
+    */
+   public boolean unregisterFailoverListener(FailoverListener l)
    {
-      return "ConnectionDelegate[" + id + ", SID=" + serverID + "]";
+      throw new IllegalStateException("This invocation should not be handled here!");
    }
 
+   /**
+    * This invocation should be handled by the client-side interceptor chain.
+    */
+   public void performFailover()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void closeValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void openValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public boolean isValveOpen()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public int getActiveThreadsCount()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
    public void init()
    {
       super.init();
@@ -249,27 +343,23 @@
    {
       this.versionToUse = versionToUse;
    }
-   
-   public void copyAttributes(DelegateSupport newDelegate)
+
+   public String toString()
    {
-      super.copyAttributes(newDelegate);
-      
-      this.remotingConnection = ((ClientConnectionDelegate)newDelegate).getRemotingConnection();
-      
-      this.versionToUse = ((ClientConnectionDelegate)newDelegate).getVersionToUse();
+      return "ConnectionDelegate[" + id + ", SID=" + serverID + "]";
    }
 
-   // Protected -----------------------------------------------------   
+   // Protected ------------------------------------------------------------------------------------
 
    protected Client getClient()
    {
       return ((ConnectionState)state).getRemotingConnection().getInvokingClient();
    }
 
-   // Package Private -----------------------------------------------
+   // Package Private ------------------------------------------------------------------------------
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner Classes -------------------------------------------------
+   // Inner Classes --------------------------------------------------------------------------------
 
 }

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionFactoryDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionFactoryDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientConnectionFactoryDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -27,6 +27,7 @@
 import javax.jms.JMSException;
 
 import org.jboss.aop.Dispatcher;
+import org.jboss.aop.Advised;
 import org.jboss.aop.joinpoint.Invocation;
 import org.jboss.aop.joinpoint.MethodInvocation;
 import org.jboss.aop.metadata.SimpleMetaData;
@@ -58,13 +59,13 @@
 public class ClientConnectionFactoryDelegate
    extends DelegateSupport implements ConnectionFactoryDelegate
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
    private static final long serialVersionUID = 2512460695662741413L;
 
    private static final Logger log = Logger.getLogger(ClientConnectionFactoryDelegate.class);
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
    //This data is needed in order to create a connection
    private String serverLocatorURI;
@@ -77,7 +78,7 @@
    
    private transient boolean trace;
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
    
    /*
     * Calculate what version to use.
@@ -90,7 +91,8 @@
 
       Version versionToUse;
 
-      if (connectionVersion.getProviderIncrementingVersion() <= clientVersion.getProviderIncrementingVersion())
+      if (connectionVersion.getProviderIncrementingVersion() <=
+          clientVersion.getProviderIncrementingVersion())
       {
          versionToUse = connectionVersion;
       }
@@ -102,39 +104,50 @@
       return versionToUse;
    }
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
-   public ClientConnectionFactoryDelegate(int objectID, int serverId, String serverLocatorURI,
-                                          Version serverVersion,
-                                          boolean clientPing)
+   public ClientConnectionFactoryDelegate(int objectID, int serverID, String serverLocatorURI,
+                                          Version serverVersion, boolean clientPing)
    {
       super(objectID);
 
-      this.serverID = serverId;
+      this.serverID = serverID;
       this.serverLocatorURI = serverLocatorURI;
       this.serverVersion = serverVersion;
       this.clientPing = clientPing;
       trace = log.isTraceEnabled();
    }
 
-   // ConnectionFactoryDelegateImplementation -----------------------
- 
-   /**
-    * This invocation should either be handled by the client-side interceptor chain or by the
-    * server-side endpoint.
-    */
-   public CreateConnectionResult createConnectionDelegate(String username, String password, int failedNodeId)
-      throws JMSException
+   // ClientAOPStackProvider implementation --------------------------------------------------------
+
+   public byte[] getClientAOPStack()
    {
-      throw new IllegalStateException("This invocation should not be handled here!");
+      // When the invocation lands here the first time, the AOP stack hasn't been initialized yet,
+      // so we "manually" add this delegate at the end of its empty stack and re-send the invocation
+      // down the stack; it will eventually end up on the server.
+
+      ((Advised)this)._getInstanceAdvisor().appendInterceptor(this);
+
+      byte[] clientAOPStack = this.getClientAOPStack();
+
+      // remove ourselves from the end of our own AOP stack now that we have the "real" AOP
+      // configuration that we can use.
+
+      ((Advised)this)._getInstanceAdvisor().removeInterceptor(this.getName());
+
+      return clientAOPStack;
    }
 
+   // ConnectionFactoryDelegate implementation -----------------------------------------------------
+ 
    /**
     * This invocation should either be handled by the client-side interceptor chain or by the
     * server-side endpoint.
-    * @see org.jboss.jms.server.endpoint.ServerConnectionFactoryEndpoint#getClientAOPConfig()
     */
-   public byte[] getClientAOPConfig()
+   public CreateConnectionResult createConnectionDelegate(String username,
+                                                          String password,
+                                                          int failedNodeID)
+      throws JMSException
    {
       throw new IllegalStateException("This invocation should not be handled here!");
    }
@@ -149,7 +162,7 @@
       throw new IllegalStateException("This invocation should not be handled here!");
    }
 
-   // Public --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
 
    public synchronized Object invoke(Invocation invocation) throws Throwable
    {
@@ -160,21 +173,31 @@
 
       SimpleMetaData md = mi.getMetaData();
 
-      md.addMetaData(Dispatcher.DISPATCHER,
-                     Dispatcher.OID,
-                     new Integer(id),
-                     PayloadKey.AS_IS);
+      if ("getClientAOPStack".equals(methodName))
+      {
+         // the ClientAOPStackProvider on the server-side is the ServerPeer itself
+         md.addMetaData(Dispatcher.DISPATCHER,
+                        Dispatcher.OID,
+                        new Integer(serverID),
+                        PayloadKey.AS_IS);
+      }
+      else
+      {
+         md.addMetaData(Dispatcher.DISPATCHER,
+                        Dispatcher.OID,
+                        new Integer(id),
+                        PayloadKey.AS_IS);
+      }
 
-      /*
-       * If the method being invoked is createConnectionDelegate then we must invoke it on the same
-       * remoting client subsequently used by the connection.
-       * This is because we need to pass in the remoting session id in the call to createConnection
-       * All other invocations can be invoked on an arbitrary client, which can be created for each invocation.
-       * If we disable pinging on the client then it is a reasonably light weight operation to create the client
-       * since it will use the already existing invoker.
-       * This prevents us from having to maintain a Client instance per connection factory, which gives
-       * difficulties in knowing when to close it.
-       */
+      // If the method being invoked is createConnectionDelegate() then we must invoke it on the
+      // same remoting client subsequently used by the connection. This is because we need to pass
+      // in the remoting session id in the call to createConnection. All other invocations can be
+      // invoked on an arbitrary client, which can be created for each invocation.
+      //
+      // If we disable pinging on the client then it is a reasonably light weight operation to
+      // create the client since it will use the already existing invoker. This prevents us from
+      // having to maintain a Client instance per connection factory, which gives difficulties in
+      // knowing when to close it.
 
       Client client;
 
@@ -208,16 +231,14 @@
          configuration.put(Client.ENABLE_LEASE, String.valueOf(false));
 
          client = new Client(new InvokerLocator(serverLocatorURI), configuration);
-
          client.setSubsystem(ServerPeer.REMOTING_JMS_SUBSYSTEM);
-
          client.connect();
 
          client.setMarshaller(new JMSWireFormat());
          client.setUnMarshaller(new JMSWireFormat());
       }
 
-      //What version should we use for invocations on this connection factory?
+      // What version should we use for invocations on this connection factory?
       Version version = getVersionToUse(serverVersion);
       byte v = version.getProviderIncrementingVersion();
 
@@ -233,8 +254,8 @@
       }
       catch (Throwable t)
       {
-         //If we were invoking createConnectionDelegate and failure occurs then we need to clear
-         //up the JMSRemotingConnection
+         // If we were invoking createConnectionDelegate and failure occurs then we need to clear
+         // up the JMSRemotingConnection
 
          if (remotingConnection != null)
          {
@@ -253,9 +274,9 @@
       {
          if (remotingConnection == null)
          {
-            //Not a call to createConnectionDelegate - disconnect the client
+            // Not a call to createConnectionDelegate - disconnect the client
 
-            //client.disconnect();
+            // client.disconnect();
          }
       }
 
@@ -317,24 +338,22 @@
       return serverVersion;
    }
    
-   public void copyAttributes(DelegateSupport newDelegate)
+   public void synchronizeWith(DelegateSupport newDelegate) throws Exception
    {
-      super.copyAttributes(newDelegate);
+      super.synchronizeWith(newDelegate);
    }
 
-   // Protected -----------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
 
-   
-
    protected Client getClient()
    {
       return null;
    }
 
-   // Package Private -----------------------------------------------
+   // Package Private ------------------------------------------------------------------------------
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner Classes -------------------------------------------------
+   // Inner Classes --------------------------------------------------------------------------------
 
 }

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientConsumerDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientConsumerDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientConsumerDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -171,6 +171,42 @@
       throw new IllegalStateException("This invocation should not be handled here!");
    }
 
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void closeValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void openValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public boolean isValveOpen()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public int getActiveThreadsCount()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
    // Public --------------------------------------------------------
 
    public String toString()
@@ -193,9 +229,9 @@
       return channelId;
    }
    
-   public void copyAttributes(DelegateSupport newDelegate)
+   public void synchronizeWith(DelegateSupport newDelegate) throws Exception
    {
-      super.copyAttributes(newDelegate);
+      super.synchronizeWith(newDelegate);
       
       this.bufferSize = ((ClientConsumerDelegate)newDelegate).getBufferSize();
       

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientProducerDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientProducerDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientProducerDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -212,6 +212,42 @@
       throw new IllegalStateException("This invocation should not be handled here!");
    }
 
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void closeValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void openValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public boolean isValveOpen()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public int getActiveThreadsCount()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
    // Public --------------------------------------------------------
 
    public String toString()

Modified: trunk/src/main/org/jboss/jms/client/delegate/ClientSessionDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClientSessionDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClientSessionDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -61,15 +61,15 @@
  */
 public class ClientSessionDelegate extends DelegateSupport implements SessionDelegate
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
    private static final long serialVersionUID = -8096852898620279131L;
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
    public ClientSessionDelegate(int objectID)
    {
@@ -79,9 +79,26 @@
    public ClientSessionDelegate()
    {      
    }
-   
-   // SessionDelegate implementation --------------------------------
 
+   // DelegateSupport overrides --------------------------------------------------------------------
+
+   public void synchronizeWith(DelegateSupport nd) throws Exception
+   {
+      super.synchronizeWith(nd);
+
+      ClientSessionDelegate newDelegate = (ClientSessionDelegate)nd;
+
+      // synchronize server endpoint state
+
+      // synchronize the delegates
+
+      // synchronize (recursively) the client-side state
+      
+      state.synchronizeWith(newDelegate.getState());
+   }
+
+   // SessionDelegate implementation ---------------------------------------------------------------
+
    /**
     * This invocation should either be handled by the client-side interceptor chain or by the
     * server-side endpoint.
@@ -189,7 +206,8 @@
     */
    public ConsumerDelegate createConsumerDelegate(JBossDestination destination, String selector,
                                                   boolean noLocal, String subscriptionName,
-                                                  boolean connectionConsumer, long failoverChannelId) throws JMSException
+                                                  boolean connectionConsumer,
+                                                  long failoverChannelId) throws JMSException
    {
       throw new IllegalStateException("This invocation should not be handled here!");
    }
@@ -447,16 +465,51 @@
    {
       throw new IllegalStateException("This invocation should not be handled here!");
    }
-   
 
-   // Public --------------------------------------------------------
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void closeValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
 
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public void openValve()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public boolean isValveOpen()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   /**
+    * This invocation should either be handled by the client-side interceptor chain or by the
+    * server-side endpoint.
+    */
+   public int getActiveThreadsCount()
+   {
+      throw new IllegalStateException("This invocation should not be handled here!");
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
    public String toString()
    {
       return "SessionDelegate[" + id + "]";
    }
 
-   // Protected -----------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
    
    protected Client getClient()
    {
@@ -465,10 +518,10 @@
    }
 
 
-   // Package Private -----------------------------------------------
+   // Package Private ------------------------------------------------------------------------------
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner Classes -------------------------------------------------
+   // Inner Classes --------------------------------------------------------------------------------
 
 }

Deleted: trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/ClusteredClientConnectionFactoryDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -1,151 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source
- * Copyright 2005, JBoss Inc., and individual contributors as indicated
- * by the @authors tag. See the copyright.txt in the distribution for a
- * full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.jboss.jms.client.delegate;
-
-import java.util.Map;
-
-import org.jboss.jms.server.Version;
-
-/**
- * A ClusteredClientConnectionFactoryDelegate
- * 
- * This ConnectionFactoryDelegate handles connection factory operations
- * (getIdBlock, getClientAOPConfig like any other connection factory)
- * 
- * The actual failover logic is in the FailoverAspect
- * 
- * @author <a href="mailto:clebert.suconic at jboss.org">Clebert Suconic</a>
- * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
- * @version <tt>$Revision: 1.1 $</tt>
- *
- * $Id$
- */
-public class ClusteredClientConnectionFactoryDelegate extends ClientConnectionFactoryDelegate
-{
-   // Constants -----------------------------------------------------
-
-   private static final long serialVersionUID = 8286850860206289277L;
-
-   // Static --------------------------------------------------------
-
-   // Attributes ----------------------------------------------------
-
-   private ClientConnectionFactoryDelegate[] delegates;
-
-   //Map <node Id, failover node id>
-   private Map failoverMap;
-
-   // Constructors --------------------------------------------------
-
-   public ClusteredClientConnectionFactoryDelegate(int objectID, int serverId,
-                                                   String serverLocatorURI,
-                                                   Version serverVersion, boolean clientPing,
-                                                   ClientConnectionFactoryDelegate[] delegates,
-                                                   Map failoverMap)
-   {
-      super(objectID, serverId, serverLocatorURI, serverVersion, clientPing);
-      this.delegates = delegates;
-      this.failoverMap = failoverMap;
-   }
-
-   // Some of the properties of ClientConnectionFactoryDelegate are not exposed.
-   // I didn't want to expose then while I needed another delegate's properties to perform a copy.
-   // So, I created this Constructor so I could have access into protected members inside an
-   // extension class.
-
-   public ClusteredClientConnectionFactoryDelegate(ClientConnectionFactoryDelegate mainDelegate,
-                                                   ClientConnectionFactoryDelegate[] delegates,
-                                                   Map failoverMap)
-   {
-      this(mainDelegate.getID(), mainDelegate.getServerID(), mainDelegate.getServerLocatorURI(),
-           mainDelegate.getServerVersion(), mainDelegate.getClientPing(), delegates, failoverMap);
-   }
-
-   // DelegateSupport overrides -------------------------------------
-
-   public void init()
-   {
-      super.init();
-
-      for (int i = 0; i < delegates.length; i++)
-      {
-         if (this!=delegates[i])
-         {
-            delegates[i].init();
-         }
-      }      
-   }
-
-   // Public --------------------------------------------------------
-
-   public ClientConnectionFactoryDelegate[] getDelegates()
-   {
-      return delegates;
-   }
-
-   public Map getFailoverMap()
-   {
-      return failoverMap;
-   }
-   
-   public void setFailoverMap(Map failoverMap)
-   {
-      this.failoverMap = failoverMap;
-   }
-   
-   public void setDelegates(ClientConnectionFactoryDelegate[] dels)
-   {
-      this.delegates = dels;
-   }
-
-   public String toString()
-   {
-      StringBuffer sb = new StringBuffer("ClusteredConnectionFactoryDelegate[");
-      sb.append(id).append("][");
-      if (delegates == null)
-      {
-         sb.append("-]");
-      }
-      else
-      {
-         for(int i = 0; i < delegates.length; i++)
-         {
-            sb.append(delegates[i].getServerID());
-            if (i < delegates.length - 1)
-            {
-               sb.append(',');
-            }
-         }
-         sb.append("]");
-      }
-      return sb.toString();
-   }
-
-   // Package protected ---------------------------------------------
-
-   // Protected -----------------------------------------------------
-
-   // Private -------------------------------------------------------
-
-   // Inner classes -------------------------------------------------
-
-}

Modified: trunk/src/main/org/jboss/jms/client/delegate/DelegateSupport.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/DelegateSupport.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/DelegateSupport.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -54,9 +54,9 @@
  *
  * $Id$
  */
-public abstract class DelegateSupport implements Interceptor, Serializable
+public abstract class DelegateSupport implements Interceptor, Serializable, Initializable
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
    private static final long serialVersionUID = 8005108339439737469L;
 
@@ -64,7 +64,7 @@
 
    private static boolean trace = log.isTraceEnabled();
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
    // This is set on the server.
    protected int id;
@@ -75,9 +75,9 @@
    // queried for many aspects in an a single invocation.
    protected transient HierarchicalState state;
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
    public DelegateSupport(int id)
    {
@@ -94,7 +94,7 @@
 
    public String getName()
    {
-      // it's needed a meaninful name to change the aop stack programatically (HA uses that)
+      // Neede a meaninful name to change the aop stack programatically (HA uses that)
       return this.getClass().getName();
    }
 
@@ -104,7 +104,7 @@
     */
    public Object invoke(Invocation invocation) throws Throwable
    {
-      if (trace) { log.trace("invoking " + ((MethodInvocation)invocation).getMethod().getName() + " on server"); }
+      if (trace) { log.trace(this + " invoking " + ((MethodInvocation)invocation).getMethod().getName() + " on server"); }
 
       invocation.getMetaData().addMetaData(Dispatcher.DISPATCHER,
                                            Dispatcher.OID,
@@ -116,13 +116,20 @@
       MessagingMarshallable request = new MessagingMarshallable(version, invocation);
       MessagingMarshallable response = (MessagingMarshallable)getClient().invoke(request, null);
 
-      if (trace) { log.trace("got server response for " + ((MethodInvocation)invocation).getMethod().getName()); }
+      if (trace) { log.trace(this + " got server response for " + ((MethodInvocation)invocation).getMethod().getName()); }
 
       return response.getLoad();
    }
 
-   // Public --------------------------------------------------------
+   // Initializable implemenation ------------------------------------------------------------------
 
+   public void init()
+   {
+      ((Advised)this)._getInstanceAdvisor().appendInterceptor(this);
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
    public HierarchicalState getState()
    {
       return state;
@@ -133,34 +140,25 @@
       this.state = state;
    }
 
-   /**
-    *  Add Invoking interceptor and prepare the stack for invocations.
-    */
-   public void init()
-   {
-      ((Advised)this)._getInstanceAdvisor().appendInterceptor(this);
-
-      checkMarshallers();
-   }
-
    public int getID()
    {
       return id;
    }
 
    /**
-    * During HA events, a new object is created on the new server and the state on that new object
-    * has to be transfered to this actual object. For example, a Connection will have to assume the
-    * ObjectID of the new connection endpoint and the new RemotingConnection.
+    * During HA events, delegates corresponding to new enpoints on the new server are created and
+    * the state of those delegates has to be transfered to the "failed" delegates. For example, a
+    * "failed" connection delegate will have to assume the ID of the new connection endpoint, the
+    * new RemotingConnection instance, etc.
     */
-   public void copyAttributes(DelegateSupport newDelegate)
+   public void synchronizeWith(DelegateSupport newDelegate) throws Exception
    {
       id = newDelegate.getID();
    }
 
-   // Package protected ---------------------------------------------
+   // Package protected ----------------------------------------------------------------------------
 
-   // Protected -----------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
 
    protected SimpleMetaData getMetaData()
    {
@@ -170,24 +168,8 @@
    protected abstract Client getClient() throws Exception;
 
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   private static boolean checked;
+   // Inner classes --------------------------------------------------------------------------------
 
-   /**
-    * Check that the correct marshallers have been registered with the MarshalFactory.
-    * This is only done once.
-    */
-   private synchronized void checkMarshallers()
-   {
-      if (checked)
-      {
-         return;
-      }
-
-      checked = true;
-   }
-
-   // Inner classes -------------------------------------------------
-
 }

Added: trunk/src/main/org/jboss/jms/client/delegate/Initializable.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/delegate/Initializable.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/delegate/Initializable.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,24 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.client.delegate;
+
+/**
+ * Applies to delegates.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public interface Initializable
+{
+   /**
+    * Usually add the delegate itself as the last invoking interceptor in the stack, and prepare
+    * the stack for invocations.
+    */
+   void init();
+}

Modified: trunk/src/main/org/jboss/jms/client/remoting/CallbackManager.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/remoting/CallbackManager.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/remoting/CallbackManager.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -40,8 +40,8 @@
  * The CallbackManager is an InvocationHandler used for handling callbacks to message consumers.
  * The callback is received and dispatched off to the relevant consumer.
  * 
- * There is one instance of this class per remoting connection - which is to a unique server - therefore
- * there is no need to add the server id to the key when doing look ups
+ * There is one instance of this class per remoting connection - which is to a unique server -
+ * therefore there is no need to add the server id to the key when doing look ups.
  * 
  * @author <a href="tim.fox at jboss.com">Tim Fox</a>
  * @author <a href="ovidiu at jboss.org">Ovidiu Feodorov</a>
@@ -51,13 +51,13 @@
  */
 public class CallbackManager implements InvokerCallbackHandler
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
    protected static final Logger log;
 
    public static final String JMS_CALLBACK_SUBSYSTEM = "CALLBACK";
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
    protected static CallbackManager theManager;
    
@@ -69,19 +69,19 @@
       trace = log.isTraceEnabled();
    }
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
    // Map<Long(lookup)-MessageCallbackHandler>
    protected Map callbackHandlers;
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
    public CallbackManager()
    {
       callbackHandlers = new ConcurrentReaderHashMap();
    }
 
-   // InvokerCallbackHandler implementation -------------------------
+   // InvokerCallbackHandler implementation --------------------------------------------------------
 
    public void handleCallback(Callback callback) throws HandleCallbackException
    {
@@ -89,7 +89,8 @@
       ClientDelivery dr = (ClientDelivery)mm.getLoad();
       MessageProxy msg = dr.getMessage();
 
-      MessageCallbackHandler handler = (MessageCallbackHandler)callbackHandlers.get(new Integer(dr.getConsumerId()));
+      MessageCallbackHandler handler =
+         (MessageCallbackHandler)callbackHandlers.get(new Integer(dr.getConsumerId()));
 
       if (handler == null)
       {
@@ -103,7 +104,7 @@
       handler.handleMessage(msg);
    }
 
-   // Public --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
 
    public void registerHandler(int consumerID, MessageCallbackHandler handler)
    {
@@ -115,12 +116,12 @@
       return (MessageCallbackHandler)callbackHandlers.remove(new Integer(consumerID));
    }
 
-   // Package protected ---------------------------------------------
+   // Package protected ----------------------------------------------------------------------------
 
-   // Protected -----------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner classes -------------------------------------------------
+   // Inner classes --------------------------------------------------------------------------------
 
 }

Modified: trunk/src/main/org/jboss/jms/client/remoting/ConsolidatedRemotingConnectionListener.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/remoting/ConsolidatedRemotingConnectionListener.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/remoting/ConsolidatedRemotingConnectionListener.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -79,7 +79,7 @@
 
          try
          {
-            log.debug(this + " forwarding remoting failure " + throwable + " to " + l);
+            log.debug(this + " forwarding remoting failure \"" + throwable + "\" to " + l);
             l.handleConnectionException(throwable, client);
          }
          catch(Exception e)
@@ -154,9 +154,9 @@
    {
       if (state == null)
       {
-         return "ConsolidatedRemotingConnectionListener(UNINITIALIZED)";
+         return "ConsolidatedListener(UNINITIALIZED)";
       }
-      return state + ".ConsolidatedRemotingConnectionListener";
+      return state + ".ConsolidatedListener";
    }
 
    // Package protected ---------------------------------------------

Modified: trunk/src/main/org/jboss/jms/client/state/BrowserState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/BrowserState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/BrowserState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -53,6 +53,10 @@
       this.messageSelector=selector;
    }
 
+   public void synchronizeWith(HierarchicalState newState) throws Exception {
+      //To change body of implemented methods use File | Settings | File Templates.
+   }
+   
    public DelegateSupport getDelegate()
    {
       return (DelegateSupport)delegate;

Modified: trunk/src/main/org/jboss/jms/client/state/ConnectionState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/ConnectionState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/ConnectionState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -22,12 +22,19 @@
 package org.jboss.jms.client.state;
 
 import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
 
 import org.jboss.jms.client.delegate.DelegateSupport;
 import org.jboss.jms.client.delegate.ClientConnectionDelegate;
+import org.jboss.jms.client.delegate.ClientSessionDelegate;
 import org.jboss.jms.client.remoting.JMSRemotingConnection;
 import org.jboss.jms.client.remoting.ConsolidatedRemotingConnectionListener;
+import org.jboss.jms.client.FailoverEvent;
+import org.jboss.jms.client.FailoverListener;
 import org.jboss.jms.delegate.ConnectionDelegate;
+import org.jboss.jms.delegate.ConnectionFactoryDelegate;
 import org.jboss.jms.message.MessageIdGenerator;
 import org.jboss.jms.server.Version;
 import org.jboss.jms.tx.ResourceManager;
@@ -43,41 +50,55 @@
  * 
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @author <a href="mailto:clebert.suconic at jboss.com">Clebert Suconic</a>
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ *
  * @version <tt>$Revision$</tt>
  *
  * $Id$
  */
 public class ConnectionState extends HierarchicalStateSupport
 {
+   // Constants ------------------------------------------------------------------------------------
+
    private static final Logger log = Logger.getLogger(ConnectionState.class);
 
-   private JMSRemotingConnection remotingConnection;
+   // Static ---------------------------------------------------------------------------------------
 
-   private ConsolidatedRemotingConnectionListener remotingConnectionListener;
+   private static boolean trace = log.isTraceEnabled();
 
-   private ResourceManager resourceManager;
+   // Attributes -----------------------------------------------------------------------------------
 
-   private MessageIdGenerator idGenerator;
-
    private int serverID;
 
    private Version versionToUse;
 
    private ConnectionDelegate delegate;
 
-   // This is filled from and for the HA interceptors only
-   private transient String user;
+   protected boolean started;
 
-   // This is filled from and for the HA interceptors only
+   private boolean justCreated = true;
+
+   private String clientID;
+
+   private JMSRemotingConnection remotingConnection;
+   private ConsolidatedRemotingConnectionListener remotingConnectionListener;
+   private ResourceManager resourceManager;
+   private MessageIdGenerator idGenerator;
+
+   // Cached by the connection state in case ClusteringAspect needs to re-try establishing
+   // connection on a different node
+   private transient String username;
+
+   // Cached by the connection state in case ClusteringAspect needs to re-try establishing
+   // connection on a different node
    private transient String password;
 
-   protected boolean started;
+   private List failoverListeners;
 
-   /** This property used to be delcared on ConnectionAspect */
-   private String clientID;
+   // needed to try re-creating connection in case failure is detected on the current connection
+   private ConnectionFactoryDelegate clusteredConnectionFactoryDelegate;
 
-    /** This property used to be delcared on ConnectionAspect */
-   private boolean justCreated = true;
+   // Constructors ---------------------------------------------------------------------------------
 
    public ConnectionState(int serverID, ConnectionDelegate delegate,
                           JMSRemotingConnection remotingConnection,
@@ -99,32 +120,37 @@
       remotingConnectionListener.setConnectionState(this);
 
       // Each connection has its own resource manager. If we can failover all connections with the
-      // same server id at the same time then we can maintain one rm per unique server as opposed to
-      // per connection.
+      // same server id at the same time then we can maintain one rm per unique server as opposed
+      // to per connection.
       this.resourceManager = ResourceManagerFactory.instance.checkOutResourceManager(serverID);
 
       this.idGenerator = gen;
       this.serverID = serverID;
+
+      failoverListeners = new ArrayList();
    }
 
-   public ResourceManager getResourceManager()
+   // HierarchicalState implementation -------------------------------------------------------------
+
+   public DelegateSupport getDelegate()
    {
-      return resourceManager;
+      return (DelegateSupport) delegate;
    }
 
-   public MessageIdGenerator getIdGenerator()
+   public void setDelegate(DelegateSupport delegate)
    {
-      return idGenerator;
+      this.delegate = (ConnectionDelegate) delegate;
    }
 
-   public JMSRemotingConnection getRemotingConnection()
+   public HierarchicalState getParent()
    {
-      return remotingConnection;
+      // A connection doesn't have a parent
+      return null;
    }
 
-   public ConsolidatedRemotingConnectionListener getRemotingConnectionListener()
+   public void setParent(HierarchicalState parent)
    {
-      return remotingConnectionListener;
+      // noop - a connection doesn't have a parent
    }
 
    public Version getVersionToUse()
@@ -132,28 +158,62 @@
       return versionToUse;
    }
 
-   public int getServerID()
+   public void synchronizeWith(HierarchicalState ns) throws Exception
    {
-      return serverID;
+      ConnectionState newState = (ConnectionState)ns;
+
+      remotingConnection = newState.remotingConnection;
+      idGenerator = newState.idGenerator;
+      serverID = newState.serverID;
+      versionToUse = newState.versionToUse;
+
+      // I removed this due to http://jira.jboss.com/jira/browse/JBMESSAGING-686
+      //this.delegate = newState.delegate;
+
+      ConnectionDelegate newDelegate = (ConnectionDelegate)newState.getDelegate();
+
+      for(Iterator i = getChildren().iterator(); i.hasNext(); )
+      {
+         SessionState sessionState = (SessionState)i.next();
+         ClientSessionDelegate sessionDelegate = (ClientSessionDelegate)sessionState.getDelegate();
+
+         // create a new session on the new connection for each session on the old connection
+         ClientSessionDelegate newSessionDelegate = (ClientSessionDelegate)newDelegate.
+            createSessionDelegate(sessionState.isTransacted(),
+                                  sessionState.getAcknowledgeMode(),
+                                  sessionState.isXA());
+
+         sessionDelegate.synchronizeWith(newSessionDelegate);
+      }
    }
 
-   public DelegateSupport getDelegate()
+   // Public ---------------------------------------------------------------------------------------
+
+   public ResourceManager getResourceManager()
    {
-      return (DelegateSupport) delegate;
+      return resourceManager;
    }
 
-   public void setDelegate(DelegateSupport delegate)
+   public MessageIdGenerator getIdGenerator()
    {
-      this.delegate = (ConnectionDelegate) delegate;
+      return idGenerator;
    }
 
-   /**
-    * Connection doesn't have a parent
-    */
-   public void setParent(HierarchicalState parent)
+   public JMSRemotingConnection getRemotingConnection()
    {
+      return remotingConnection;
    }
 
+   public ConsolidatedRemotingConnectionListener getRemotingConnectionListener()
+   {
+      return remotingConnectionListener;
+   }
+
+   public int getServerID()
+   {
+      return serverID;
+   }
+
    public boolean isStarted()
    {
       return started;
@@ -174,14 +234,14 @@
       this.password = password;
    }
 
-   public String getUser()
+   public String getUsername()
    {
-      return user;
+      return username;
    }
 
-   public void setUser(String user)
+   public void setUsername(String username)
    {
-      this.user = user;
+      this.username = username;
    }
 
    public String getClientID()
@@ -204,29 +264,69 @@
       this.justCreated = justCreated;
    }
 
-   /**
-    * Connection doesn't have a parent
-    */
-   public HierarchicalState getParent()
+   public void broadcastFailoverEvent(FailoverEvent e)
    {
-      return null;
+      if (trace) { log.trace(this + " broadcasting " + e); }
+
+      List listenersCopy;
+
+      synchronized(failoverListeners)
+      {
+         listenersCopy = new ArrayList(failoverListeners);
+      }
+
+      for(Iterator i = listenersCopy.iterator(); i.hasNext(); )
+      {
+         FailoverListener listener = (FailoverListener)i.next();
+
+         try
+         {
+            listener.failoverEventOccured(e);
+         }
+         catch(Exception ex)
+         {
+            log.warn("Failover listener " + listener + " did not accept event", ex);
+         }
+      }
    }
 
-   //When failing over a connection, we keep the old connection's state but there are certain fields
-   //we need to update
-   public void copyState(ConnectionState newState)
+   public void registerFailoverListener(FailoverListener listener)
    {
-      this.remotingConnection = newState.remotingConnection;
-      this.idGenerator = newState.idGenerator;
-      this.serverID = newState.serverID;
-      this.versionToUse = newState.versionToUse;
+      synchronized(failoverListeners)
+      {
+         failoverListeners.add(listener);
+      }
+   }
 
-      // I removed this due to http://jira.jboss.com/jira/browse/JBMESSAGING-686
-      //this.delegate = newState.delegate;
+   public boolean unregisterFailoverListener(FailoverListener listener)
+   {
+      synchronized(failoverListeners)
+      {
+         return failoverListeners.remove(listener);
+      }
    }
 
+   public void setClusteredConnectionFactoryDeleage(ConnectionFactoryDelegate d)
+   {
+      this.clusteredConnectionFactoryDelegate = d;
+   }
+
+   public ConnectionFactoryDelegate getClusteredConnectionFactoryDelegate()
+   {
+      return clusteredConnectionFactoryDelegate;
+   }
+
    public String toString()
    {
       return "ConnectionState[" + ((ClientConnectionDelegate)delegate).getID() + "]";
    }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner classes --------------------------------------------------------------------------------
+
 }

Modified: trunk/src/main/org/jboss/jms/client/state/ConsumerState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/ConsumerState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/ConsumerState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -134,6 +134,10 @@
       return parent.getVersionToUse();
    }
 
+   public void synchronizeWith(HierarchicalState newState) throws Exception {
+      //To change body of implemented methods use File | Settings | File Templates.
+   }
+
    public int getBufferSize()
    {
       return bufferSize;

Modified: trunk/src/main/org/jboss/jms/client/state/HierarchicalState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/HierarchicalState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/HierarchicalState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -32,6 +32,7 @@
  * 
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @author <a href="mailto:clebert.suconic at jboss.com">Clebert Suconic</a>
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
  * @version <tt>$Revision$</tt>
  *
  * $Id$
@@ -49,4 +50,22 @@
    void setParent(HierarchicalState parent);
 
    Version getVersionToUse();
+
+   /**
+    * Closes children's failover valves, by sending closeValve() invocations down children's
+    * delegate stack. It is NOT intended to be recursive, unless the children chose so.
+    */
+   void closeChildrensValves() throws Exception;
+
+   /**
+    * Opens children's failover valves, by sending openValve() invocations down children's
+    * delegate stack. It is NOT intended to be recursive, unless the children chose so.
+    */
+   void openChildrensValves() throws Exception;
+
+   /**
+    * Update my own state based on the new state.
+    */
+   void synchronizeWith(HierarchicalState newState) throws Exception;
+
 }

Modified: trunk/src/main/org/jboss/jms/client/state/HierarchicalStateSupport.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/HierarchicalStateSupport.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/HierarchicalStateSupport.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -22,11 +22,12 @@
 package org.jboss.jms.client.state;
 
 import java.util.Set;
+import java.util.Iterator;
 
 import org.jboss.jms.client.delegate.DelegateSupport;
+import org.jboss.jms.client.Valve;
 
 /**
- * 
  * Base implementation of HierarchicalState.
  * 
  * State is created and maintained by the StateCreationAspect. The state is placed in the meta data
@@ -36,48 +37,80 @@
  * 
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @author <a href="mailto:clebert.suconic at jboss.com">Clebert Suconic</a>
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ *
  * @version <tt>$Revision$</tt>
  *
  * $Id$
  */
 public abstract class HierarchicalStateSupport implements HierarchicalState
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
+   // Set<HierarchicalState>
    protected Set children;
 
+   // Static ---------------------------------------------------------------------------------------
 
-   // Static --------------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
-   // Constructors --------------------------------------------------
-
    public HierarchicalStateSupport(HierarchicalState parent, DelegateSupport delegate)
    {
-      this.setParent(parent);
-      this.setDelegate(delegate); // TODO - find a more elegant solution, delegate must implement an interface that has getID()
+      setParent(parent);
+      setDelegate(delegate); // TODO - find a more elegant solution, delegate must implement an interface that has getID()
       if (parent != null)
       {
          parent.getChildren().add(this);
       }
    }
 
-   // HierarchicalState implementation -------------------------------
+   // HierarchicalState implementation -------------------------------------------------------------
 
+   /**
+    * @return Set<HierarchicalState>
+    */
    public Set getChildren()
    {
       return children;
    }
 
+   public void closeChildrensValves() throws Exception
+   {
+      if (children == null)
+      {
+         return;
+      }
 
-   // Public --------------------------------------------------------
+      for(Iterator i = children.iterator(); i.hasNext(); )
+      {
+         HierarchicalState s = (HierarchicalState)i.next();
+         ((Valve)s.getDelegate()).closeValve();
+      }
+   }
 
-   // Protected ------------------------------------------------------
+   public void openChildrensValves() throws Exception
+   {
+      if (children == null)
+      {
+         return;
+      }
 
-   // Package Private ------------------------------------------------
+      for(Iterator i = children.iterator(); i.hasNext(); )
+      {
+         HierarchicalState s = (HierarchicalState)i.next();
+         ((Valve)s.getDelegate()).openValve();
+      }
+   }
 
-   // Private --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
 
-   // Inner Classes --------------------------------------------------
+   // Protected ------------------------------------------------------------------------------------
+
+   // Package Private ------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner Classes --------------------------------------------------------------------------------
 }

Modified: trunk/src/main/org/jboss/jms/client/state/ProducerState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/ProducerState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/ProducerState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -63,6 +63,10 @@
       children = Collections.EMPTY_SET;
       this.destination = dest;
    }
+
+   public void synchronizeWith(HierarchicalState newState) throws Exception {
+      //To change body of implemented methods use File | Settings | File Templates.
+   }
    
    public Destination getDestination()
    {

Modified: trunk/src/main/org/jboss/jms/client/state/SessionState.java
===================================================================
--- trunk/src/main/org/jboss/jms/client/state/SessionState.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/client/state/SessionState.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -26,19 +26,31 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
 
 import org.jboss.jms.client.delegate.ClientSessionDelegate;
 import org.jboss.jms.client.delegate.DelegateSupport;
+import org.jboss.jms.client.delegate.ClientConnectionDelegate;
+import org.jboss.jms.client.delegate.ClientConsumerDelegate;
+import org.jboss.jms.client.delegate.ClientProducerDelegate;
+import org.jboss.jms.client.delegate.ClientBrowserDelegate;
 import org.jboss.jms.client.remoting.MessageCallbackHandler;
+import org.jboss.jms.client.remoting.CallbackManager;
 import org.jboss.jms.delegate.SessionDelegate;
 import org.jboss.jms.server.Version;
 import org.jboss.jms.server.endpoint.DeliveryInfo;
+import org.jboss.jms.server.endpoint.DeliveryRecovery;
 import org.jboss.jms.tx.MessagingXAResource;
+import org.jboss.jms.tx.ResourceManager;
+import org.jboss.jms.destination.JBossDestination;
 import org.jboss.logging.Logger;
 
 import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
 import EDU.oswego.cs.dl.util.concurrent.QueuedExecutor;
 
+import javax.jms.Session;
+
 /**
  * State corresponding to a session. This state is acessible inside aspects/interceptors.
  * 
@@ -50,97 +62,230 @@
  */
 public class SessionState extends HierarchicalStateSupport
 {
+
+   // Constants ------------------------------------------------------------------------------------
+
    protected static Logger log = Logger.getLogger(SessionState.class);
 
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private ConnectionState parent;
+   private SessionDelegate delegate;
+
    private int sessionId;
-   
    private int acknowledgeMode;
-   
    private boolean transacted;
-   
    private boolean xa;
-   
+
    private MessagingXAResource xaResource;
-   
    private Object currentTxId;
-   
+
    // Executor used for executing onMessage methods
    private QueuedExecutor executor;
-   
+
    private boolean recoverCalled;
 
    // List<DeliveryInfo>
    private List clientAckList;
-   
+
    // List<DeliveryInfo>
    //private List clientCancelList;
    
    private DeliveryInfo autoAckInfo;
+   private Map callbackHandlers;
 
-   private ConnectionState parent;
-   
-   private SessionDelegate delegate;
-   
-   private Map callbackHandlers;     
-   
+
+   // Constructors ---------------------------------------------------------------------------------
+
    public SessionState(ConnectionState parent, ClientSessionDelegate delegate,
                        boolean transacted, int ackMode, boolean xa)
    {
       super(parent, (DelegateSupport)delegate);
-      
+
       this.sessionId = delegate.getID();
-      
+
       children = new HashSet();
       this.acknowledgeMode = ackMode;
       this.transacted = transacted;
       this.xa = xa;
-      
+
       if (xa)
       {
          // Create an XA resource
-         xaResource = new MessagingXAResource(parent.getResourceManager(), this);                            
+         xaResource = new MessagingXAResource(parent.getResourceManager(), this);
       }
-      
+
       // If session is transacted and XA, the currentTxId will be updated when the XAResource will
       // be enrolled with a global transaction.
-      
+
       if (transacted & !xa)
       {
          // Create a local tx
-         currentTxId = parent.getResourceManager().createLocalTx();        
+         currentTxId = parent.getResourceManager().createLocalTx();
       }
-      
+
       executor = new QueuedExecutor(new LinkedQueue());
-      
+
       clientAckList = new ArrayList();
-      
+
      // clientCancelList = new ArrayList();
       
       // TODO could optimise this to use the same map of callbackmanagers (which holds refs
       // to callbackhandlers) in the connection, instead of maintaining another map
       callbackHandlers = new HashMap();
    }
-   
-   
-   public void setParent(HierarchicalState parent)
+
+
+   // HierarchicalState implementation -------------------------------------------------------------
+
+   public DelegateSupport getDelegate()
    {
-      this.parent = (ConnectionState)parent;
+      return (DelegateSupport)delegate;
    }
+
+   public void setDelegate(DelegateSupport delegate)
+   {
+      this.delegate = (SessionDelegate)delegate;
+   }
+
    public HierarchicalState getParent()
    {
       return parent;
    }
-   
-   public DelegateSupport getDelegate()
+
+   public void setParent(HierarchicalState parent)
    {
-      return (DelegateSupport)delegate;
+      this.parent = (ConnectionState)parent;
    }
-   
-   public void setDelegate(DelegateSupport delegate)
+
+   public Version getVersionToUse()
    {
-      this.delegate=(SessionDelegate)delegate;
+      return parent.getVersionToUse();
    }
-      
+
+   // HierarchicalStateSupport overrides -----------------------------------------------------------
+
+   public void synchronizeWith(HierarchicalState ns) throws Exception
+   {
+      SessionState newState = (SessionState)ns;
+
+      int oldSessionID = sessionId;
+      sessionId = newState.sessionId;
+
+      ClientSessionDelegate newDelegate = (ClientSessionDelegate)newState.getDelegate();
+
+      for (Iterator i = getChildren().iterator(); i.hasNext(); )
+      {
+         HierarchicalState child = (HierarchicalState)i.next();
+
+         if (child instanceof ProducerState)
+         {
+            handleFailoverOnProducer((ProducerState)child, newDelegate);
+         }
+         else if (child instanceof ConsumerState)
+         {
+            handleFailoverOnConsumer((ClientConnectionDelegate)getParent().getDelegate(),
+                                     (ConsumerState)child, newDelegate);
+         }
+         else if (child instanceof BrowserState)
+         {
+             handleFailoverOnBrowser((BrowserState)child, newDelegate);
+         }
+      }
+
+      ConnectionState connState = (ConnectionState)getParent();
+      ResourceManager rm = connState.getResourceManager();
+
+      // We need to failover from one session ID to another in the resource manager
+      rm.handleFailover(connState.getServerID(), oldSessionID, newState.sessionId);
+
+      List ackInfos = Collections.EMPTY_LIST;
+
+      if (!isTransacted() || (isXA() && getCurrentTxId() == null))
+      {
+         // Non transacted session or an XA session with no transaction set (it falls back
+         // to auto_ack)
+
+         log.debug(this + " is not transacted (or XA with no tx set), " +
+            "retrieving deliveries from session state");
+
+         // We remove any unacked non-persistent messages - this is because we don't want to ack
+         // them since the server won't know about them and will get confused
+
+         if (getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+         {
+            for(Iterator i = getClientAckList().iterator(); i.hasNext(); )
+            {
+               DeliveryInfo info = (DeliveryInfo)i.next();
+               if (!info.getMessageProxy().getMessage().isReliable())
+               {
+                  i.remove();
+                  log.debug("removed non persistent delivery " + info);
+               }
+            }
+
+            ackInfos = getClientAckList();
+         }
+         else
+         {
+            DeliveryInfo autoAck = getAutoAckInfo();
+            if (autoAck != null)
+            {
+               if (!autoAck.getMessageProxy().getMessage().isReliable())
+               {
+                  // unreliable, discard
+                  setAutoAckInfo(null);
+               }
+               else
+               {
+                  // reliable
+                  ackInfos = new ArrayList();
+                  ackInfos.add(autoAck);
+               }
+            }
+         }
+
+         log.debug(this + " retrieved " + ackInfos.size() + " deliveries");
+      }
+      else
+      {
+         // Transacted session - we need to get the acks from the resource manager. BTW we have
+         // kept the old resource manager
+
+         ackInfos = rm.getDeliveriesForSession(getSessionId());
+      }
+
+      if (!ackInfos.isEmpty())
+      {
+         SessionDelegate nd = (SessionDelegate)getDelegate();
+
+         List recoveryInfos = new ArrayList();
+
+         for (Iterator i = ackInfos.iterator(); i.hasNext(); )
+         {
+            DeliveryInfo info = (DeliveryInfo)i.next();
+
+            DeliveryRecovery recInfo =
+               new DeliveryRecovery(info.getMessageProxy().getDeliveryId(),
+                                    info.getMessageProxy().getMessage().getMessageID(),
+                                    info.getChannelId());
+
+            recoveryInfos.add(recInfo);
+         }
+
+         log.debug(this + " sending delivery recovery info: " + recoveryInfos);
+         nd.recoverDeliveries(recoveryInfos);
+      }
+      else
+      {
+         log.debug(this + " no delivery recovery info to send");
+      }
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
    /**
     * @return List<AckInfo>
     */
@@ -148,12 +293,12 @@
    {
       return clientAckList;
    }
-   
+
    public DeliveryInfo getAutoAckInfo()
    {
       return autoAckInfo;
    }
-   
+
    public void setAutoAckInfo(DeliveryInfo info)
    {
       if (info != null && autoAckInfo != null)
@@ -162,82 +307,198 @@
       }
       autoAckInfo = info;
    }
-   
+
    public int getAcknowledgeMode()
    {
       return acknowledgeMode;
    }
-   
+
    public boolean isTransacted()
    {
       return transacted;
    }
-   
+
    public boolean isXA()
    {
       return xa;
    }
-   
+
    public MessagingXAResource getXAResource()
    {
       return xaResource;
    }
-   
+
    public QueuedExecutor getExecutor()
    {
       return executor;
    }
-   
+
    public Object getCurrentTxId()
    {
       return currentTxId;
    }
-   
+
    public boolean isRecoverCalled()
    {
       return recoverCalled;
    }
-   
+
    public void setCurrentTxId(Object id)
    {
       this.currentTxId = id;
    }
-   
-   public Version getVersionToUse()
-   {
-      return parent.getVersionToUse();
-   }
-   
+
    public void setRecoverCalled(boolean recoverCalled)
    {
       this.recoverCalled = recoverCalled;
    }
-   
+
    public MessageCallbackHandler getCallbackHandler(int consumerID)
    {
       return (MessageCallbackHandler)callbackHandlers.get(new Integer(consumerID));
    }
-   
+
    public void addCallbackHandler(MessageCallbackHandler handler)
    {
       callbackHandlers.put(new Integer(handler.getConsumerId()), handler);
    }
-   
+
    public void removeCallbackHandler(MessageCallbackHandler handler)
    {
       callbackHandlers.remove(new Integer(handler.getConsumerId()));
    }
-      
+
    public int getSessionId()
    {
       return sessionId;
    }
-   
-   // When failing over a session, we keep the old session's state but there are certain fields
-   // we need to update
-   public void copyState(SessionState newState)
-   {       
-      this.sessionId = newState.sessionId;
+
+   public String toString()
+   {
+      return "SessionState[" + sessionId + "]";
    }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   /**
+    * TODO See http://jira.jboss.org/jira/browse/JBMESSAGING-708
+    */
+   private void handleFailoverOnConsumer(ClientConnectionDelegate failedConnectionDelegate,
+                                         ConsumerState failedConsumerState,
+                                         ClientSessionDelegate newSessionDelegate)
+
+      throws Exception
+   {
+      log.debug(this + " failing over consumer " + failedConsumerState);
+
+      CallbackManager oldCallbackManager =
+         failedConnectionDelegate.getRemotingConnection().getCallbackManager();
+
+      ClientConsumerDelegate failedConsumerDelegate =
+         (ClientConsumerDelegate)failedConsumerState.getDelegate();
+
+      log.debug(this + " creating alternate consumer");
+
+      ClientConsumerDelegate newConsumerDelegate = (ClientConsumerDelegate)newSessionDelegate.
+         createConsumerDelegate((JBossDestination)failedConsumerState.getDestination(),
+                                failedConsumerState.getSelector(),
+                                failedConsumerState.isNoLocal(),
+                                failedConsumerState.getSubscriptionName(),
+                                failedConsumerState.isConnectionConsumer(),
+                                failedConsumerState.getChannelId());
+
+      log.debug(this + " alternate consumer created");
+
+      // Copy the attributes from the new consumer to the old consumer
+      failedConsumerDelegate.synchronizeWith(newConsumerDelegate);
+
+      ConsumerState newState = (ConsumerState)newConsumerDelegate.getState();
+
+      int oldConsumerID = failedConsumerState.getConsumerID();
+
+      // Update attributes on the old state
+      failedConsumerState.copyState(newState);
+
+      // We need to re-use the existing message callback handler
+
+      MessageCallbackHandler oldHandler =
+         oldCallbackManager.unregisterHandler(oldConsumerID);
+
+      ConnectionState newConnectionState = (ConnectionState)failedConnectionDelegate.getState();
+
+      CallbackManager newCallbackManager =
+         newConnectionState.getRemotingConnection().getCallbackManager();
+
+      // Remove the new handler
+      MessageCallbackHandler newHandler = newCallbackManager.
+         unregisterHandler(newState.getConsumerID());
+
+      log.debug("New handler is " + System.identityHashCode(newHandler));
+
+      //But we need to update some fields from the new one
+      oldHandler.copyState(newHandler);
+
+      //Now we re-register the old handler with the new callback manager
+
+      newCallbackManager.registerHandler(newState.getConsumerID(),
+                                         oldHandler);
+
+      // We don't need to add the handler to the session state since it is already there - we
+      // are re-using the old handler
+
+      log.debug(this + " failed over consumer");
+   }
+
+
+   /**
+    * TODO see http://jira.jboss.org/jira/browse/JBMESSAGING-709
+    */
+   private void handleFailoverOnProducer(ProducerState failedProducerState,
+                                         ClientSessionDelegate failedSessionDelegate)
+      throws Exception
+   {
+      ClientProducerDelegate newProducerDelegate = (ClientProducerDelegate)failedSessionDelegate.
+         createProducerDelegate((JBossDestination)failedProducerState.getDestination());
+
+      ClientProducerDelegate failedProducerDelegate =
+         (ClientProducerDelegate)failedProducerState.getDelegate();
+
+      failedProducerDelegate.synchronizeWith(newProducerDelegate);
+      failedProducerState.copyState((ProducerState)newProducerDelegate.getState());
+
+      log.debug("handling fail over on producerDelegate " + failedProducerDelegate + " destination=" + failedProducerState.getDestination());
+   }
+
+
+   /**
+    * TODO see http://jira.jboss.org/jira/browse/JBMESSAGING-710
+    */
+   private void handleFailoverOnBrowser(BrowserState failedBrowserState,
+                                        ClientSessionDelegate failedSessionDelegate)
+      throws Exception
+   {
+      ClientBrowserDelegate newBrowserDelegate = (ClientBrowserDelegate)failedSessionDelegate.
+         createBrowserDelegate(failedBrowserState.getJmsDestination(),
+                               failedBrowserState.getMessageSelector());
+
+      ClientBrowserDelegate failedBrowserDelegate =
+         (ClientBrowserDelegate)failedBrowserState.getDelegate();
+
+      failedBrowserDelegate.synchronizeWith(newBrowserDelegate);
+      failedBrowserState.copyState((BrowserState)newBrowserDelegate.getState());
+
+      log.debug("handling fail over on browserDelegate " + failedBrowserDelegate + " destination=" + failedBrowserState.getJmsDestination() + " selector=" + failedBrowserState.getMessageSelector());
+
+   }
+
+
+   // Inner classes --------------------------------------------------------------------------------
+
+
+
 }
 

Modified: trunk/src/main/org/jboss/jms/delegate/BrowserDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/BrowserDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/BrowserDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -22,6 +22,7 @@
 package org.jboss.jms.delegate;
 
 import org.jboss.jms.server.endpoint.BrowserEndpoint;
+import org.jboss.jms.client.Valve;
 
 /**
  * Represents the minimal set of operations to provide browser
@@ -32,8 +33,8 @@
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @version <tt>$Revision$</tt>
  */
-public interface BrowserDelegate extends BrowserEndpoint
-{   
+public interface BrowserDelegate extends Valve, BrowserEndpoint
+{
 }
    
 

Modified: trunk/src/main/org/jboss/jms/delegate/ConnectionDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/ConnectionDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/ConnectionDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -28,6 +28,8 @@
 import javax.jms.ServerSessionPool;
 
 import org.jboss.jms.client.JBossConnectionConsumer;
+import org.jboss.jms.client.FailoverListener;
+import org.jboss.jms.client.Valve;
 import org.jboss.jms.server.endpoint.ConnectionEndpoint;
 
 /**
@@ -40,7 +42,7 @@
  *
  * $Id$
  */
-public interface ConnectionDelegate extends ConnectionEndpoint
+public interface ConnectionDelegate extends Valve, ConnectionEndpoint
 {      
    ExceptionListener getExceptionListener() throws JMSException;
    
@@ -53,4 +55,10 @@
                                                     String messageSelector,
                                                     ServerSessionPool sessionPool,
                                                     int maxMessages) throws JMSException;
+
+   void registerFailoverListener(FailoverListener failoverListener);
+   boolean unregisterFailoverListener(FailoverListener failoverListener);
+
+   void performFailover() throws Exception;
+
 }

Modified: trunk/src/main/org/jboss/jms/delegate/ConnectionFactoryDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/ConnectionFactoryDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/ConnectionFactoryDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -22,6 +22,7 @@
 package org.jboss.jms.delegate;
 
 import org.jboss.jms.server.endpoint.ConnectionFactoryEndpoint;
+import org.jboss.jms.client.ClientAOPStackProvider;
 
 /**
  * Represents the minimal set of operations to provide connection factory
@@ -33,6 +34,6 @@
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @version <tt>$Revision$</tt>
  */
-public interface ConnectionFactoryDelegate extends ConnectionFactoryEndpoint
+public interface ConnectionFactoryDelegate extends ClientAOPStackProvider, ConnectionFactoryEndpoint
 {
 }

Modified: trunk/src/main/org/jboss/jms/delegate/ConsumerDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/ConsumerDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/ConsumerDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -27,6 +27,7 @@
 
 import org.jboss.jms.destination.JBossDestination;
 import org.jboss.jms.server.endpoint.ConsumerEndpoint;
+import org.jboss.jms.client.Valve;
 
 /**
  * Represents the minimal set of operations to provide consumer
@@ -40,17 +41,17 @@
  *
  * $Id$
  */
-public interface ConsumerDelegate extends ConsumerEndpoint
+public interface ConsumerDelegate extends Valve, ConsumerEndpoint
 {
    MessageListener getMessageListener();
-   
+
    void setMessageListener(MessageListener listener);
-   
+
    JBossDestination getDestination();
-   
+
    boolean getNoLocal();
-   
-   String getMessageSelector();   
-   
+
+   String getMessageSelector();
+
    Message receive(long timeout) throws JMSException;
 }

Modified: trunk/src/main/org/jboss/jms/delegate/ProducerDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/ProducerDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/ProducerDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -25,6 +25,7 @@
 import javax.jms.Message;
 
 import org.jboss.jms.client.Closeable;
+import org.jboss.jms.client.Valve;
 import org.jboss.jms.destination.JBossDestination;
 
 /**
@@ -34,7 +35,7 @@
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @version <tt>$Revision$</tt>
  */
-public interface ProducerDelegate extends Closeable
+public interface ProducerDelegate extends Valve, Closeable
 {
    void setDisableMessageID(boolean value) throws JMSException;
    

Modified: trunk/src/main/org/jboss/jms/delegate/SessionDelegate.java
===================================================================
--- trunk/src/main/org/jboss/jms/delegate/SessionDelegate.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/delegate/SessionDelegate.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -37,6 +37,7 @@
 import org.jboss.jms.message.TextMessageProxy;
 import org.jboss.jms.server.endpoint.DeliveryInfo;
 import org.jboss.jms.server.endpoint.SessionEndpoint;
+import org.jboss.jms.client.Valve;
 
 /**
  * Represents the minimal set of operations to provide session functionality.
@@ -48,12 +49,12 @@
  *
  * $Id$
  */
-public interface SessionDelegate extends SessionEndpoint
-{   
+public interface SessionDelegate extends Valve, SessionEndpoint
+{
    MessageProxy createMessage() throws JMSException;
-   
+
    BytesMessageProxy createBytesMessage() throws JMSException;
-  
+
    MapMessageProxy createMapMessage() throws JMSException;
 
    ObjectMessageProxy createObjectMessage() throws JMSException;
@@ -63,37 +64,37 @@
    StreamMessageProxy createStreamMessage() throws JMSException;
 
    TextMessageProxy createTextMessage() throws JMSException;
-   
+
    TextMessageProxy createTextMessage(String text) throws JMSException;
-   
+
    void preDeliver(DeliveryInfo deliveryInfo) throws JMSException;
-   
+
    void postDeliver() throws JMSException;
-   
+
    MessageListener getMessageListener() throws JMSException;
-   
+
    void setMessageListener(MessageListener listener) throws JMSException;
-   
+
    void run();
-   
+
    XAResource getXAResource();
-   
+
    void addAsfMessage(MessageProxy m, int consumerID, long channelID,
                       int maxDeliveries, SessionDelegate connectionConsumerDelegate);
-   
+
    boolean getTransacted();
-   
+
    int getAcknowledgeMode();
-   
+
    void commit() throws JMSException;
-   
+
    void rollback() throws JMSException;
-   
+
    void recover() throws JMSException;
-   
+
    void redeliver(List deliveryInfos) throws JMSException;
-   
+
    ProducerDelegate createProducerDelegate(JBossDestination destination) throws JMSException;
-   
+
    void acknowledgeAll() throws JMSException;
 }

Modified: trunk/src/main/org/jboss/jms/server/ConnectionFactoryManager.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/ConnectionFactoryManager.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/ConnectionFactoryManager.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -36,8 +36,6 @@
    /**
     * @param jndiBindings - if null, the connection factory will be created and registered with the
     *        AOP subsystem, but not bound in JNDI.
-    *
-    * @return an identifier that uniques identifies the registered ConnectionFactory.
     */
    void registerConnectionFactory(String uniqueName, String clientID, JNDIBindings jndiBindings,
                                  String locatorURI, boolean clientPing,

Modified: trunk/src/main/org/jboss/jms/server/ServerPeer.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/ServerPeer.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/ServerPeer.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -40,11 +40,14 @@
 import org.jboss.jms.server.connectormanager.SimpleConnectorManager;
 import org.jboss.jms.server.destination.ManagedQueue;
 import org.jboss.jms.server.endpoint.ServerSessionEndpoint;
+import org.jboss.jms.server.endpoint.advised.ClientAOPStackProviderAdvised;
 import org.jboss.jms.server.plugin.contract.JMSUserManager;
 import org.jboss.jms.server.remoting.JMSServerInvocationHandler;
 import org.jboss.jms.server.remoting.JMSWireFormat;
+import org.jboss.jms.server.remoting.JMSDispatcher;
 import org.jboss.jms.server.security.SecurityMetadataStore;
 import org.jboss.jms.util.ExceptionUtil;
+import org.jboss.jms.client.ClientAOPStackProvider;
 import org.jboss.logging.Logger;
 import org.jboss.messaging.core.Queue;
 import org.jboss.messaging.core.memory.MemoryManager;
@@ -81,7 +84,7 @@
  *
  * $Id$
  */
-public class ServerPeer extends ServiceMBeanSupport
+public class ServerPeer extends ServiceMBeanSupport implements ClientAOPStackProvider
 {
    // Constants -----------------------------------------------------
 
@@ -96,7 +99,7 @@
    // Attributes ----------------------------------------------------
 
    private int serverPeerID;
-   private byte[] clientAOPConfig;
+   private byte[] clientAOPStack;
    private Version version;
 
    private String defaultQueueJNDIContext;
@@ -246,6 +249,10 @@
          
          initializeRemoting(mbeanServer);
 
+         // Register myself as ClientAOPStackProvider with the JMSDispatcher
+         ClientAOPStackProviderAdvised advised = new ClientAOPStackProviderAdvised(this);
+         JMSDispatcher.instance.registerTarget(new Integer(serverPeerID), advised);
+
          started = true;
 
          log.info("JBoss Messaging " + getVersion().getProviderVersion() + " server [" +
@@ -597,6 +604,13 @@
       }
    }
 
+   // ClientAOPStackProvider implementation -------------------------------
+
+   public byte[] getClientAOPStack()
+   {
+      return clientAOPStack;
+   }
+
    // Public --------------------------------------------------------
    
    public IDManager getMessageIDManager()
@@ -710,11 +724,6 @@
       return started;
    }
 
-   public byte[] getClientAOPConfig()
-   {
-      return clientAOPConfig;
-   }
-
    public Version getVersion()
    {
       return version;
@@ -774,8 +783,7 @@
       // We get the reference lazily to avoid problems with MBean circular dependencies
       if (postOffice == null)
       {
-         postOffice = (PostOffice)getServer().
-            getAttribute(postOfficeObjectName, "Instance");
+         postOffice = (PostOffice)getServer().getAttribute(postOfficeObjectName, "Instance");
 
          // We also inject the replicator dependency into the ConnectionFactoryJNDIMapper. This is
          // a bit messy but we have a circular dependency POJOContainer should be able to help us
@@ -788,8 +796,8 @@
             rep.registerListener(new FailoverListener());
          }
          
-         //Also need to inject into txRepository
-         this.txRepository.injectPostOffice(postOffice);
+         // Also need to inject into txRepository
+         txRepository.injectPostOffice(postOffice);
       }
       return postOffice;
    }
@@ -978,7 +986,7 @@
             os.write(b);
          }
          os.flush();
-         clientAOPConfig = os.toByteArray();
+         clientAOPStack = os.toByteArray();
       }
       finally
       {

Modified: trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactory.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactory.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactory.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -70,32 +70,36 @@
    {
       try
       {
+         log.debug(this + " starting");
+
          started = true;
          
          if (connectorObjectName == null)
          {
-            throw new IllegalArgumentException("A Connector must be specified for each Connection Factory");
+            throw new IllegalArgumentException("A Connector must be specified for " +
+                                               "each Connection Factory");
          }
          
          if (serverPeerObjectName == null)
          {
-            throw new IllegalArgumentException("ServerPeer must be specified for each Connection Factory");
+            throw new IllegalArgumentException("ServerPeer must be specified for " +
+                                               "each Connection Factory");
          }
       
          String locatorURI = (String)server.getAttribute(connectorObjectName, "InvokerLocator");
-         
          ServerPeer serverPeer = (ServerPeer)server.getAttribute(serverPeerObjectName, "Instance");
          
          connectionFactoryManager = serverPeer.getConnectionFactoryManager();
          connectorManager = serverPeer.getConnectorManager();
          connectionManager = serverPeer.getConnectionManager();
-         
-         long leasePeriod = ((Long)server.getAttribute(connectorObjectName, "LeasePeriod")).longValue();
-         
+
+         int refCount = connectorManager.registerConnector(connectorObjectName.getCanonicalName());
+
+         long leasePeriod =
+            ((Long)server.getAttribute(connectorObjectName, "LeasePeriod")).longValue();
+
          // if leasePeriod <= 0, disable pinging altogether
-         
-         int refCount = connectorManager.registerConnector(connectorObjectName.getCanonicalName());
-         
+
          boolean enablePing = leasePeriod > 0;
          
          if (refCount == 1 && enablePing)
@@ -109,15 +113,15 @@
          // We use the MBean service name to uniquely identify the connection factory
          
          connectionFactoryManager.
-            registerConnectionFactory(getServiceName().toString(), clientID, jndiBindings, locatorURI,
-                                      enablePing, prefetchSize, defaultTempQueueFullSize,
-                                      defaultTempQueuePageSize, defaultTempQueueDownCacheSize,
-                                      clustered);
+            registerConnectionFactory(getServiceName().toString(), clientID, jndiBindings,
+                                      locatorURI, enablePing, prefetchSize,
+                                      defaultTempQueueFullSize, defaultTempQueuePageSize,
+                                      defaultTempQueueDownCacheSize, clustered);
       
          InvokerLocator locator = new InvokerLocator(locatorURI);
 
-         String info =
-            "Connector " + locator.getProtocol() + "://" + locator.getHost() + ":" + locator.getPort();
+         String info = "Connector " + locator.getProtocol() + "://" +
+            locator.getHost() + ":" + locator.getPort();
                  
          if (enablePing)
          {
@@ -129,7 +133,7 @@
          }
       
          log.info(info);
-         log.info(this + " deployed");
+         log.info(this + " started");
       }
       catch (Throwable t)
       {

Modified: trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactoryJNDIMapper.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactoryJNDIMapper.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/connectionfactory/ConnectionFactoryJNDIMapper.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -30,6 +30,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.naming.Context;
 import javax.naming.InitialContext;
@@ -37,7 +38,7 @@
 
 import org.jboss.jms.client.JBossConnectionFactory;
 import org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate;
-import org.jboss.jms.client.delegate.ClusteredClientConnectionFactoryDelegate;
+import org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate;
 import org.jboss.jms.server.ConnectionFactoryManager;
 import org.jboss.jms.server.ServerPeer;
 import org.jboss.jms.server.Version;
@@ -45,6 +46,7 @@
 import org.jboss.jms.server.endpoint.advised.ConnectionFactoryAdvised;
 import org.jboss.jms.server.remoting.JMSDispatcher;
 import org.jboss.jms.util.JNDIUtil;
+import org.jboss.jms.delegate.ConnectionFactoryDelegate;
 import org.jboss.logging.Logger;
 import org.jboss.messaging.core.plugin.contract.ReplicationListener;
 import org.jboss.messaging.core.plugin.contract.Replicator;
@@ -72,8 +74,6 @@
 
    // Attributes ----------------------------------------------------
 
-   private boolean trace = log.isTraceEnabled();
-
    protected Context initialContext;
    protected ServerPeer serverPeer;
 
@@ -85,11 +85,9 @@
 
    private Replicator replicator;
 
-   /*
-   We cache the map of node->failover node in here.
-   This is then updated when node joins or leaves the cluster via the replicationListener
-   When new cfs are deployed we use the cached map
-   */
+   // Map<Integer(nodeID)->Integer(failoverNodeID)>
+   // The map is updated when a node joins of leaves the cluster via the replicationListener. When
+   // a new ConnectionFactories are deployed we use the cached map.
    protected Map failoverMap;
 
    // Constructors --------------------------------------------------
@@ -115,12 +113,14 @@
                                                       boolean clustered)
       throws Exception
    {
-      log.debug(this + " registering connection factory '" + uniqueName + "', bindings: " + jndiBindings);
+      log.debug(this + " registering connection factory '" + uniqueName +
+         "', bindings: " + jndiBindings);
 
       // Sanity check
       if (delegates.containsKey(uniqueName))
       {
-         throw new IllegalArgumentException("There's already a connection factory registered with name " + uniqueName);
+         throw new IllegalArgumentException("There's already a connection factory " +
+                                            "registered with name " + uniqueName);
       }
 
       int id = serverPeer.getNextObjectID();
@@ -134,16 +134,12 @@
                                              defaultTempQueueDownCacheSize);
       endpoints.put(uniqueName, endpoint);
 
-      ClientConnectionFactoryDelegate delegate = null;
+      ConnectionFactoryDelegate delegate = null;
 
       if (clustered)
       {
          setupReplicator();
       }
-      else
-      {
-         log.info("ConnectionFactoryJNDIMapper is non clustered");
-      }
 
       boolean creatingClustered = clustered && replicator != null;
 
@@ -151,6 +147,8 @@
          new ClientConnectionFactoryDelegate(id, serverPeer.getServerPeerID(),
                                              locatorURI, version, clientPing);
 
+      log.debug(this + " created local delegate " + localDelegate);
+
       // When registering a new clustered connection factory I should first create it with the
       // available delegates then send the replication message. We then listen for connection
       // factories added to global state using the replication listener and then update their
@@ -169,6 +167,7 @@
          Map localDelegates = replicator.get(CF_PREFIX + uniqueName);
          delegate = createClusteredDelegate(localDelegates.values());
 
+         log.debug(this + " created clustered delegate " + delegate);
       }
       else
       {
@@ -216,8 +215,7 @@
       log.trace("Removing delegate from delegates list with key=" + uniqueName + " at serverPeerID=" +
                   this.serverPeer.getServerPeerID());
 
-      ClientConnectionFactoryDelegate delegate =
-         (ClientConnectionFactoryDelegate)delegates.remove(uniqueName);
+      ConnectionFactoryDelegate delegate = (ConnectionFactoryDelegate)delegates.remove(uniqueName);
 
       if (delegate == null)
       {
@@ -293,7 +291,7 @@
             // failoverMap and rebind all connection factories with the new mapping. We cannot just
             // reference a single map since the objects are bound in JNDI in serialized form.
 
-            recalculateFailoverMap(updatedReplicantMap);
+            failoverMap = recalculateFailoverMap(updatedReplicantMap.keySet());
 
             // Rebind
 
@@ -304,8 +302,8 @@
                ServerConnectionFactoryEndpoint endpoint =
                   (ServerConnectionFactoryEndpoint)entry.getValue();
 
-               ClusteredClientConnectionFactoryDelegate del =
-                  (ClusteredClientConnectionFactoryDelegate)delegates.get(uniqueName);
+               ClientClusteredConnectionFactoryDelegate del =
+                  (ClientClusteredConnectionFactoryDelegate)delegates.get(uniqueName);
 
                if (del == null)
                {
@@ -328,15 +326,15 @@
 
             log.debug(this + " received '" + uniqueName + "' connection factory update " + updatedReplicantMap);
 
-            ClusteredClientConnectionFactoryDelegate del =
-               (ClusteredClientConnectionFactoryDelegate)delegates.get(uniqueName);
+            ClientClusteredConnectionFactoryDelegate del =
+               (ClientClusteredConnectionFactoryDelegate)delegates.get(uniqueName);
 
             if (del == null)
             {
                throw new IllegalStateException("Cannot find cf with name " + uniqueName);
             }
 
-            List newDels = sortCFS(updatedReplicantMap.values());
+            List newDels = sortDelegatesOnServerID(updatedReplicantMap.values());
 
             ClientConnectionFactoryDelegate[] delArr =
                (ClientConnectionFactoryDelegate[])newDels.
@@ -382,25 +380,29 @@
 
    private void setupReplicator() throws Exception
    {
-      this.serverPeer.getPostOfficeInstance();
+      serverPeer.getPostOfficeInstance();
    }
 
-   private void recalculateFailoverMap(Map nodeAddressMap) throws Exception
+   /**
+    * @param nodeIDs Set<Integer(nodeID)>
+    * @return Map<Integer(nodeID)->Integer(failoverNodeID)>
+    */
+   private Map recalculateFailoverMap(Set nodeIDs) throws Exception
    {
       FailoverMapper mapper = replicator.getFailoverMapper();
-      failoverMap = mapper.generateMapping(nodeAddressMap.keySet());
+      return mapper.generateMapping(nodeIDs);
    }
 
    /**
-    * @param localDelegates - Map<Integer(nodeId) - ClientConnectionFactoryDelegate>
+    * @param localDelegates - Collection<ClientConnectionFactoryDelegate>
     */
-   private ClusteredClientConnectionFactoryDelegate
-      createClusteredDelegate(Collection localDelegates)  throws Exception
+   private ClientClusteredConnectionFactoryDelegate
+      createClusteredDelegate(Collection localDelegates) throws Exception
    {
-      if (trace) { log.trace(this + " updating failover delegates with " + localDelegates); }
+      log.trace(this + " creating a clustered ConnectionFactoryDelegate based on " + localDelegates);
 
-      // First sort the local delegates in order of server id
-      List sortedLocalDelegates = sortCFS(localDelegates);
+      // First sort the local delegates in order of server ID
+      List sortedLocalDelegates = sortDelegatesOnServerID(localDelegates);
 
       ClientConnectionFactoryDelegate[] delegates =
          (ClientConnectionFactoryDelegate[])sortedLocalDelegates.
@@ -417,39 +419,14 @@
             throw new IllegalStateException("Cannot find address node mapping!");
          }
 
-         recalculateFailoverMap(nodeAddressMap);
+         failoverMap = recalculateFailoverMap(nodeAddressMap.keySet());
       }
 
-      // The main delegated is needed for the construction of
-      // ClusteredClientConnectionFactoryDelegate. ClusteredClientConnectionFactoryDelegate extends
-      // ClientConnectionFactoryDelegate and it will need the current server's delegate properties
-      // to be bound to ObjectId, ServerLocator and other connection properties.
-      // The ClusteredCFDelegate will copy these properties on its contructor defined bellow after
-      // this loop.
-
-      ClientConnectionFactoryDelegate mainDelegate = null;
-
-      for(Iterator i = localDelegates.iterator(); i.hasNext();)
-      {
-         ClientConnectionFactoryDelegate del = (ClientConnectionFactoryDelegate)i.next();
-
-         if (del.getServerID() == serverPeer.getServerPeerID())
-         {
-            // sanity check
-            if (mainDelegate != null)
-            {
-               throw new IllegalStateException("There are two servers with serverID=" +
-                        this.serverPeer.getServerPeerID() + ", verify your clustering configuration");
-            }
-            mainDelegate = del;
-         }
-      }
-
-      return new ClusteredClientConnectionFactoryDelegate(mainDelegate, delegates, failoverMap);
+      return new ClientClusteredConnectionFactoryDelegate(delegates, failoverMap);
    }
 
    private void rebindConnectionFactory(Context ic, JNDIBindings jndiBindings,
-                                        ClientConnectionFactoryDelegate delegate)
+                                        ConnectionFactoryDelegate delegate)
       throws NamingException
    {
       JBossConnectionFactory cf = new JBossConnectionFactory(delegate);
@@ -466,10 +443,7 @@
       }
    }
 
-   /**
-    * Sort the collection of delegates in order of server id
-    */
-   private List sortCFS(Collection delegates)
+   private List sortDelegatesOnServerID(Collection delegates)
    {
       List localDels = new ArrayList(delegates);
 

Modified: trunk/src/main/org/jboss/jms/server/endpoint/ConnectionFactoryEndpoint.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/ConnectionFactoryEndpoint.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/ConnectionFactoryEndpoint.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -39,11 +39,9 @@
 { 
    CreateConnectionResult createConnectionDelegate(String username,
                                                    String password, 
-                                                   int failedNodeId)
+                                                   int failedNodeID)
       throws JMSException;
    
-   byte[] getClientAOPConfig() throws JMSException;
-
    IDBlock getIdBlock(int size) throws JMSException;
 }
 

Modified: trunk/src/main/org/jboss/jms/server/endpoint/DeliveryInfo.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/DeliveryInfo.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/DeliveryInfo.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -127,7 +127,7 @@
 
    public String toString()
    {
-      return "Delivery[" + msg + "]";
+      return "Delivery[" + getDeliveryId() + ", " + msg + "]";
    }
 
    // Ack Implementation  -------------------------------------------

Modified: trunk/src/main/org/jboss/jms/server/endpoint/ServerConnectionFactoryEndpoint.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/ServerConnectionFactoryEndpoint.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/ServerConnectionFactoryEndpoint.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -172,18 +172,6 @@
       return new ClientConnectionDelegate(connectionID, serverPeer.getServerPeerID());
    }
    
-   public byte[] getClientAOPConfig() throws JMSException
-   {
-      try
-      {
-         return serverPeer.getClientAOPConfig();
-      }
-      catch (Throwable t)
-      {
-         throw ExceptionUtil.handleJMSInvocation(t, this + " getClientAOPConfig");
-      }
-   }
-
    public IDBlock getIdBlock(int size) throws JMSException
    {
       try

Modified: trunk/src/main/org/jboss/jms/server/endpoint/ServerConsumerEndpoint.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/ServerConsumerEndpoint.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/ServerConsumerEndpoint.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -29,7 +29,6 @@
 import org.jboss.jms.message.JBossMessage;
 import org.jboss.jms.message.MessageProxy;
 import org.jboss.jms.selector.Selector;
-import org.jboss.jms.server.destination.ManagedDestination;
 import org.jboss.jms.server.remoting.JMSDispatcher;
 import org.jboss.jms.server.remoting.MessagingMarshallable;
 import org.jboss.jms.util.ExceptionUtil;

Added: trunk/src/main/org/jboss/jms/server/endpoint/advised/ClientAOPStackProviderAdvised.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/advised/ClientAOPStackProviderAdvised.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/advised/ClientAOPStackProviderAdvised.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,57 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.jms.server.endpoint.advised;
+
+import org.jboss.jms.server.ServerPeer;
+import org.jboss.jms.client.ClientAOPStackProvider;
+
+/**
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ *
+ * $Id$
+ */
+public class ClientAOPStackProviderAdvised implements ClientAOPStackProvider
+{
+   // Constants -----------------------------------------------------
+
+   // Static --------------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   private ServerPeer serverPeer;
+
+   // Constructors --------------------------------------------------
+
+   public ClientAOPStackProviderAdvised(ServerPeer serverPeer)
+   {
+      this.serverPeer = serverPeer;
+   }
+
+   // ClientAOPStackProvider implementation --------------------------------
+
+   public byte[] getClientAOPStack()
+   {
+      return serverPeer.getClientAOPStack();
+   }
+
+   // Public --------------------------------------------------------
+
+   public String toString()
+   {
+      return "ClientAOPStackProviderAdvised->" + serverPeer;
+   }
+
+   // Package protected ---------------------------------------------
+
+   // Protected -----------------------------------------------------
+
+   // Private -------------------------------------------------------
+
+   // Inner classes -------------------------------------------------
+
+}

Modified: trunk/src/main/org/jboss/jms/server/endpoint/advised/ConnectionFactoryAdvised.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/endpoint/advised/ConnectionFactoryAdvised.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/endpoint/advised/ConnectionFactoryAdvised.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -65,11 +65,6 @@
       return endpoint.createConnectionDelegate(username, password, failedNodeId);
    }
 
-   public byte[] getClientAOPConfig() throws JMSException
-   {
-      return endpoint.getClientAOPConfig();
-   }
-
    public IDBlock getIdBlock(int size) throws JMSException
    {
       return endpoint.getIdBlock(size);

Modified: trunk/src/main/org/jboss/jms/server/remoting/JMSDispatcher.java
===================================================================
--- trunk/src/main/org/jboss/jms/server/remoting/JMSDispatcher.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/server/remoting/JMSDispatcher.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -25,8 +25,8 @@
 
 /**
  * 
- * We have our own Dispatcher so we don't have the possibility with conflicts with other
- * systems using the same object ids
+ * We have our own Dispatcher so we don't have the possibility with conflicts with other systems
+ * using the same object ids.
  * 
  * @author <a href="tim.fox at jboss.com">Tim Fox</a>
  * @version 1.1

Modified: trunk/src/main/org/jboss/jms/tx/ResourceManagerFactory.java
===================================================================
--- trunk/src/main/org/jboss/jms/tx/ResourceManagerFactory.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/jms/tx/ResourceManagerFactory.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -25,14 +25,14 @@
 import java.util.Map;
 
 /**
- * This class manages instances of ResourceManager. It ensures there is one instance per instance
- * of JMS server as specified by the server id.
+ * This class manages ResourceManager instances. It ensures there is one instance per instance of
+ * JMS server as specified by the server ID.
  * 
- * This allows different JMS connections to the same JMS server (the underlying resource is the JMS server)
- * to use the same resource manager.
+ * This allows different JMS connections to the same JMS server (the underlying resource is the JMS
+ * server) to use the same resource manager.
  * 
- * This means isSameRM() on XAResource returns true, allowing the Transaction manager to join work in one
- * tx to another thus allowing 1PC optimization which should help performance.
+ * This means isSameRM() on XAResource returns true, allowing the Transaction manager to join work
+ * in one tx to another thus allowing 1PC optimization which should help performance.
  *
  * @author <a href="mailto:tim.fox at jboss.com">Tim Fox</a>
  * @version $Revision: 1329 $
@@ -40,107 +40,120 @@
  * $Id: ResourceManagerFactory.java 1329 2006-09-20 21:29:56Z ovidiu.feodorov at jboss.com $
  */
 public class ResourceManagerFactory
-{      
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   // Static ---------------------------------------------------------------------------------------
+
    public static ResourceManagerFactory instance = new ResourceManagerFactory();
-   
+
+   // Attributes -----------------------------------------------------------------------------------
+
    private Map holders;
-   
+
+   // Constructors ---------------------------------------------------------------------------------
+
    private ResourceManagerFactory()
-   {      
+   {
       holders = new HashMap();
    }
-   
-   /*
-    * Need to failover rm from old server id to new server id, merging resource managers if it already exists
-    */
-   public synchronized void handleFailover(int oldServerId, int newServerId)
-   {       
-      Holder hOld = (Holder)holders.remove(new Integer(oldServerId));
-      
-      if (hOld == null)
-      {
-         //This is ok - this would happen if there are more than one connections for the old server failing
-         //in which only the first one to fail would failover the resource manager factory - since they
-         //share the same rm
-         return;
-      }
-      
-      ResourceManager oldRM = hOld.rm;
-      
-      ResourceManager newRM = null;
-      
-      Holder hNew = (Holder)holders.get(new Integer(newServerId));      
-      
-      if (hNew != null)
-      {
-         //Need to merge into the new
-         
-         newRM = hNew.rm;
-         
-         newRM.merge(oldRM);         
-      }
-      else
-      {
-         //re-register the old rm with the new id
-         
-         Holder h = new Holder(oldRM);
-         
-         holders.put(new Integer(newServerId), h);
-      }  
-   }
-   
+
+   // Public ---------------------------------------------------------------------------------------
+
    public synchronized int size()
    {
       return holders.size();
    }
-      
+
    public synchronized boolean containsResourceManager(int serverID)
    {
       return holders.containsKey(new Integer(serverID));
    }
-   
+
    /**
     * @param serverID - server peer ID.
     */
    public synchronized ResourceManager checkOutResourceManager(int serverID)
    {
       Integer i = new Integer(serverID);
-      
+
       Holder h = (Holder)holders.get(i);
-      
+
       if (h == null)
       {
          h = new Holder();
-         
          holders.put(i, h);
       }
       else
       {
          h.refCount++;
       }
-      
+
       return h.rm;
    }
-   
+
    public synchronized void checkInResourceManager(int serverID)
    {
       Integer i = new Integer(serverID);
-      
       Holder h = (Holder)holders.get(i);
-      
+
       if (h == null)
       {
          throw new IllegalArgumentException("Cannot find resource manager for server: " + serverID);
       }
-      
+
       h.refCount--;
-      
+
       if (h.refCount == 0)
       {
          holders.remove(i);
-      }      
+      }
    }
-   
+
+   /**
+    * Need to failover rm from old server id to new server id, merging resource managers if it
+    * already exists.
+    */
+   public synchronized void handleFailover(int oldServerID, int newServerID)
+   {
+      Holder hOld = (Holder)holders.remove(new Integer(oldServerID));
+
+      if (hOld == null)
+      {
+         // This is ok - this would happen if there are more than one connections for the old server
+         // failing in which only the first one to fail would failover the resource manager factory
+         // - since they share the same rm.
+         return;
+      }
+
+      ResourceManager oldRM = hOld.rm;
+      ResourceManager newRM = null;
+      Holder hNew = (Holder)holders.get(new Integer(newServerID));
+
+      if (hNew != null)
+      {
+         // need to merge into the new
+
+         newRM = hNew.rm;
+         newRM.merge(oldRM);
+      }
+      else
+      {
+         // re-register the old rm with the new id
+
+         Holder h = new Holder(oldRM);
+         holders.put(new Integer(newServerID), h);
+      }
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   // Private --------------------------------------------------------------------------------------
+
+   // Inner classes --------------------------------------------------------------------------------
+
    private static class Holder
    {
       ResourceManager rm;
@@ -157,5 +170,4 @@
       
       int refCount = 1;
    }
-  
 }

Modified: trunk/src/main/org/jboss/messaging/core/tx/TransactionRepository.java
===================================================================
--- trunk/src/main/org/jboss/messaging/core/tx/TransactionRepository.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/src/main/org/jboss/messaging/core/tx/TransactionRepository.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -176,7 +176,7 @@
 
       prepared = persistenceManager.retrievePreparedTransactions();
 
-      if (trace) log.trace ("Found " + prepared.size() + " transactions in prepared state:");
+      if (trace) log.trace ("found " + prepared.size() + " transactions in prepared state");
 
       if (prepared != null)
       {
@@ -186,7 +186,7 @@
          {
             PreparedTxInfo txInfo = (PreparedTxInfo) iter.next();
 
-            if (trace) log.trace("Reinstating TX(XID: " + txInfo.getXid() + ", LocalId " + txInfo.getTxId() +")");
+            if (trace) log.trace("reinstating TX(XID: " + txInfo.getXid() + ", LocalId " + txInfo.getTxId() +")");
             
             Transaction tx = createTransaction(txInfo);
             

Modified: trunk/tests/build.xml
===================================================================
--- trunk/tests/build.xml	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/build.xml	2006-12-31 10:51:38 UTC (rev 1880)
@@ -358,7 +358,7 @@
 
    <target name="http-tests" depends="tests-jar, prepare-testdirs, clear-test-logs">
       <antcall target="remote-tests">
-            <param name="test.remoting" value="http"/>
+         <param name="test.remoting" value="http"/>
       </antcall>
    </target>
 

Added: trunk/tests/src/org/jboss/test/messaging/jms/AOPStackInitializationTest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/AOPStackInitializationTest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/AOPStackInitializationTest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,80 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.test.messaging.jms;
+
+import org.jboss.test.messaging.MessagingTestCase;
+import org.jboss.test.messaging.tools.ServerManagement;
+
+import javax.naming.InitialContext;
+import javax.jms.ConnectionFactory;
+import javax.jms.Connection;
+
+/**
+ * Tests the very first server invocation, when the client-side AOP stack is initialized.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision: 1843 $</tt>
+ *
+ * $Id: JMSTest.java 1843 2006-12-21 23:41:19Z timfox $
+ */
+public class AOPStackInitializationTest extends MessagingTestCase
+{
+   // Constants -----------------------------------------------------
+
+   // Static --------------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   InitialContext ic;
+
+   // Constructors --------------------------------------------------
+
+   public AOPStackInitializationTest(String name)
+   {
+      super(name);
+   }
+
+   // Public --------------------------------------------------------
+
+   public void testSimpleInitialization() throws Exception
+   {
+      ConnectionFactory cf = (ConnectionFactory)ic.lookup("/ConnectionFactory");
+
+      Connection conn = cf.createConnection();
+
+      conn.close();
+   }
+
+   // Package protected ---------------------------------------------
+
+   // Protected -----------------------------------------------------
+
+   protected void setUp() throws Exception
+   {
+      super.setUp();
+
+      ServerManagement.start("all");
+
+      ic = new InitialContext(ServerManagement.getJNDIEnvironment());
+
+      log.debug("setup done");
+   }
+
+   protected void tearDown() throws Exception
+   {
+      ic.close();
+
+      ServerManagement.stop();
+
+      super.tearDown();
+   }
+
+   // Private -------------------------------------------------------
+
+   // Inner classes -------------------------------------------------
+
+}

Added: trunk/tests/src/org/jboss/test/messaging/jms/FailoverValveTest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/FailoverValveTest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/FailoverValveTest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,909 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.test.messaging.jms;
+
+import org.jboss.test.messaging.MessagingTestCase;
+import org.jboss.test.messaging.tools.ServerManagement;
+import org.jboss.jms.client.Valve;
+import org.jboss.jms.client.JBossConnection;
+import org.jboss.jms.client.JBossSession;
+import org.jboss.jms.client.JBossQueueBrowser;
+import org.jboss.jms.client.JBossMessageProducer;
+import org.jboss.jms.client.JBossMessageConsumer;
+import org.jboss.jms.client.state.HierarchicalState;
+import org.jboss.jms.client.state.HierarchicalStateSupport;
+import org.jboss.jms.client.delegate.DelegateSupport;
+import org.jboss.jms.client.container.FailoverValveInterceptor;
+import org.jboss.jms.server.Version;
+import org.jboss.aop.joinpoint.Invocation;
+import org.jboss.aop.joinpoint.MethodInvocation;
+import org.jboss.aop.MethodInfo;
+import org.jboss.aop.util.MethodHashing;
+import org.jboss.aop.advice.Interceptor;
+import org.jboss.remoting.Client;
+
+import javax.naming.InitialContext;
+import javax.jms.Connection;
+import javax.jms.Session;
+import javax.jms.ConnectionFactory;
+import javax.jms.Queue;
+import javax.jms.MessageProducer;
+import javax.jms.MessageConsumer;
+import javax.jms.QueueBrowser;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+import java.util.Iterator;
+
+import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
+import EDU.oswego.cs.dl.util.concurrent.Slot;
+
+/**
+ * This is not necessarily a clustering test, as the failover valves are installed even for a
+ * non-clustered configuration.
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision: 1843 $</tt>
+ *
+ * $Id: JMSTest.java 1843 2006-12-21 23:41:19Z timfox $
+ */
+public class FailoverValveTest extends MessagingTestCase
+{
+   // Constants ------------------------------------------------------------------------------------
+
+   // Static ---------------------------------------------------------------------------------------
+
+   // Attributes -----------------------------------------------------------------------------------
+
+   private InitialContext ic;
+   private ConnectionFactory cf;
+   private Queue queue;
+
+   // Constructors ---------------------------------------------------------------------------------
+
+   public FailoverValveTest(String name)
+   {
+      super(name);
+   }
+
+   // Public ---------------------------------------------------------------------------------------
+
+   public void testCloseValveHierarchy() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         conn = cf.createConnection();
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+
+         Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         assertTrue(((Valve)((JBossSession)session).getDelegate()).isValveOpen());
+
+         MessageProducer prod = session.createProducer(queue);
+         assertTrue(((Valve)((JBossMessageProducer)prod).getDelegate()).isValveOpen());
+
+         MessageConsumer cons = session.createConsumer(queue);
+         assertTrue(((Valve)((JBossMessageConsumer)cons).getDelegate()).isValveOpen());
+
+         QueueBrowser browser = session.createBrowser(queue);
+         assertTrue(((Valve)((JBossQueueBrowser)browser).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().closeValve();
+
+         log.debug("top level valve closed");
+
+         assertFalse(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossSession)session).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageProducer)prod).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageConsumer)cons).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossQueueBrowser)browser).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().openValve();
+
+         log.debug("top level valve open");
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageProducer)prod).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageConsumer)cons).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossQueueBrowser)browser).getDelegate()).isValveOpen());
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+
+   }
+
+   public void testCloseValveHierarchy2() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         conn = cf.createConnection();
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+
+         Session session1 = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         Session session2 = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         assertTrue(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+
+         MessageProducer prod1 = session1.createProducer(queue);
+         assertTrue(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+
+         MessageConsumer cons1 = session1.createConsumer(queue);
+         assertTrue(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+
+         QueueBrowser browser1 = session1.createBrowser(queue);
+         assertTrue(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+
+         MessageProducer prod2 = session2.createProducer(queue);
+         assertTrue(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+
+         MessageConsumer cons2 = session2.createConsumer(queue);
+         assertTrue(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+
+         QueueBrowser browser2 = session2.createBrowser(queue);
+         assertTrue(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().closeValve();
+
+         log.debug("top level valve closed");
+
+         assertFalse(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().openValve();
+
+         log.debug("top level valve open");
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+   }
+
+   public void testValveOpenByDefault() throws Throwable
+   {
+      FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      assertEquals(Boolean.TRUE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      // try to open the valve again, it should be a noop
+      valve.invoke(buildInvocation("openValve", target));
+
+      assertEquals(Boolean.TRUE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+   }
+
+   public void testPassThroughOpenValve() throws Throwable
+   {
+      final FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      final SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      // send a thread through the open valve
+      Thread t = new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("businessMethod1", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("businessMethod1 invocation failed", t);
+            }
+         }
+      }, "Business Thread 1");
+
+      t.start();
+      t.join();
+
+      // the thread should have passed through the open valve
+      assertEquals(1, target.getBusinessMethod1InvocationCount());
+      assertEquals(1, target.getActiveThreadsHighWaterMark());
+
+   }
+
+   public void testFlipValve() throws Throwable
+   {
+      FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      valve.invoke(buildInvocation("closeValve", target));
+
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      // child states also must have been notified to close their corresponding valves
+      ParentHierarchicalState state = (ParentHierarchicalState)target.getState();
+
+      for(Iterator i = state.getChildren().iterator(); i.hasNext(); )
+      {
+         ChildState cs = (ChildState)i.next();
+         assertFalse(((Valve)cs.getDelegate()).isValveOpen());
+      }
+
+      // try to close the valve again, it should be a noop
+
+      valve.invoke(buildInvocation("closeValve", target));
+
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+   }
+
+   public void testFlipValve2() throws Throwable
+   {
+      FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      valve.invoke(buildInvocation("closeValve", target));
+
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      // child states also must have been notified to close their corresponding valves
+      ParentHierarchicalState state = (ParentHierarchicalState)target.getState();
+
+      for(Iterator i = state.getChildren().iterator(); i.hasNext(); )
+      {
+         ChildState cs = (ChildState)i.next();
+         assertFalse(((Valve)cs.getDelegate()).isValveOpen());
+      }
+
+      // re-open the valve
+
+      valve.invoke(buildInvocation("openValve", target));
+
+      assertEquals(Boolean.TRUE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      // child states also must have been notified to open their corresponding valves
+      state = (ParentHierarchicalState)target.getState();
+
+      for(Iterator i = state.getChildren().iterator(); i.hasNext(); )
+      {
+         ChildState cs = (ChildState)i.next();
+         assertTrue(((Valve)cs.getDelegate()).isValveOpen());
+      }
+   }
+
+   /**
+    * Close the valve and send a thread through it. The thread must be put on hold until the valve
+    * is opened again.
+    */
+   public void testCloseValveOneBusinessThread() throws Throwable
+   {
+      final FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      final SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      valve.invoke(buildInvocation("closeValve", target));
+
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+
+      log.debug("the valve is closed");
+
+      // smack a thread into the closed valve
+      Thread t = new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("businessMethod1", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("businessMethod1 invocation failed", t);
+            }
+         }
+      }, "Business Thread 1");
+
+      t.start();
+
+      // wait for 10 secs for the invocation to reach target object. It shouldn't ...
+      long waitTime = 10000;
+      log.info("probing target for " + waitTime / 1000 + " seconds ...");
+      InvocationToken arrived = target.waitForInvocation(waitTime);
+
+      if (arrived != null)
+      {
+         fail(arrived.getMethodName() + "() reached target, while it shouldn't have!");
+      }
+
+
+      log.debug("the business thread didn't go through");
+
+      // open the valve
+
+      valve.invoke(buildInvocation("openValve", target));
+
+      // the business invocation should complete almost immediately; wait on mutex to avoid race
+      // condition
+
+      arrived = target.waitForInvocation(2000);
+
+      assertEquals("businessMethod1", arrived.getMethodName());
+      assertEquals(1, target.getBusinessMethod1InvocationCount());
+      assertEquals(1, target.getActiveThreadsHighWaterMark());
+   }
+
+
+   /**
+    * Close the valve and send three threads through it. The threads must be put on hold until the
+    * valve is opened again.
+    */
+   public void testCloseValveThreeBusinessThread() throws Throwable
+   {
+      final FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      final SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      valve.invoke(buildInvocation("closeValve", target));
+
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+
+      log.debug("the valve is closed");
+
+      // smack thread 1 into the closed valve
+      new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("businessMethod1", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("businessMethod1 invocation failed", t);
+            }
+         }
+      }, "Business Thread 1").start();
+
+      // smack thread 2 into the closed valve
+      new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("businessMethod1", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("businessMethod1 invocation failed", t);
+            }
+         }
+      }, "Business Thread 2").start();
+
+      // smack thread 3 into the closed valve
+      new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("businessMethod2", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("businessMethod2 invocation failed", t);
+            }
+         }
+      }, "Business Thread 3").start();
+
+      // wait for 10 secs for any invocation to reach target object. It shouldn't ...
+      long waitTime = 10000;
+      log.info("probing target for " + waitTime / 1000 + " seconds ...");
+      InvocationToken arrived = target.waitForInvocation(waitTime);
+
+      if (arrived != null)
+      {
+         fail(arrived.getMethodName() + "() reached target, while it shouldn't have!");
+      }
+
+      log.debug("the business threads didn't go through");
+
+      // open the valve
+
+      valve.invoke(buildInvocation("openValve", target));
+
+      // the business invocations should complete almost immediately; wait on mutex to avoid race
+      // condition
+
+      arrived = target.waitForInvocation(2000);
+      assertNotNull(arrived);
+
+      arrived = target.waitForInvocation(2000);
+      assertNotNull(arrived);
+
+      arrived = target.waitForInvocation(2000);
+      assertNotNull(arrived);
+
+      // wait for 3 secs for any invocation to reach target object. It shouldn't ...
+      waitTime = 3000;
+      log.info("probing target for " + waitTime / 1000 + " seconds ...");
+      arrived = target.waitForInvocation(waitTime);
+
+      if (arrived != null)
+      {
+         fail("Extra " + arrived.getMethodName() + "() reached target, " +
+            "while it shouldn't have!");
+      }
+
+      assertEquals(2, target.getBusinessMethod1InvocationCount());
+      assertEquals(1, target.getBusinessMethod2InvocationCount());
+      assertEquals(3, target.getActiveThreadsHighWaterMark());
+   }
+
+   /**
+    * The current standard behavior is that the valve cannot be closed as long as there are
+    * active threads. closeValve() will block undefinitely.
+    */
+   public void testCloseWhileActiveThreads() throws Throwable
+   {
+      final FailoverValveInterceptor valve = new FailoverValveInterceptor();
+      final SimpleInvocationTargetObject target = new SimpleInvocationTargetObject(valve);
+
+      assertEquals(Boolean.TRUE, valve.invoke(buildInvocation("isValveOpen", target)));
+
+      // send a long running thread through the valve
+      new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("blockingBusinessMethod", target));
+            }
+            catch(Throwable t)
+            {
+               log.error("blockingBusinessMethod invocation failed", t);
+            }
+         }
+      }, "Long running business thread").start();
+
+      // allow blockingBusinessMethod time to block
+      Thread.sleep(2000);
+
+      assertEquals(new Integer(1), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      final Slot closingCompleted = new Slot();
+
+      // from a different thread try to close the valve; this thread will block until we unblock
+      // the business method
+
+      new Thread(new Runnable()
+      {
+         public void run()
+         {
+            try
+            {
+               valve.invoke(buildInvocation("closeValve", target));
+               closingCompleted.put(Boolean.TRUE);
+
+            }
+            catch(Throwable t)
+            {
+               log.error("blockingBusinessMethod() invocation failed", t);
+            }
+         }
+      }, "Valve closing thread").start();
+
+
+      // closing shouldn't be completed for a long time .... actually never, if I don't unblock
+      // the business method
+
+      // wait for 15 secs for closing. It shouldn't ...
+      long waitTime = 15000;
+      log.info("probing closing for " + waitTime / 1000 + " seconds ...");
+
+      Boolean closed = (Boolean)closingCompleted.poll(waitTime);
+
+      if (closed != null)
+      {
+         fail("closeValve() went through, while it shouldn't have!");
+      }
+
+      assertEquals(Boolean.TRUE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(1), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+
+      log.info("valve still open ...");
+
+      // unblock blockingBusinessMethod
+      target.unblockBlockingBusinessMethod();
+
+      // valve closing should complete immediately after that
+      closed = (Boolean)closingCompleted.poll(1000);
+      assertTrue(closed.booleanValue());
+      assertEquals(Boolean.FALSE, valve.invoke(buildInvocation("isValveOpen", target)));
+      assertEquals(new Integer(0), valve.invoke(buildInvocation("getActiveThreadsCount", target)));
+   }
+
+   // Package protected ----------------------------------------------------------------------------
+
+   // Protected ------------------------------------------------------------------------------------
+
+   protected void setUp() throws Exception
+   {
+      super.setUp();
+
+      ServerManagement.start("all");
+
+      ic = new InitialContext(ServerManagement.getJNDIEnvironment());
+
+      ServerManagement.deployQueue("TestQueue");
+
+      cf = (ConnectionFactory)ic.lookup("/ConnectionFactory");
+      queue = (Queue)ic.lookup("/queue/TestQueue");
+
+      log.debug("setup done");
+   }
+
+   protected void tearDown() throws Exception
+   {
+      ServerManagement.undeployQueue("TestQueue");
+
+      ic.close();
+
+      super.tearDown();
+   }
+
+   // Private --------------------------------------------------------------------------------------
+
+   private Invocation buildInvocation(String methodName, Object targetObject)
+      throws Exception
+   {
+      long hash =
+         MethodHashing.calculateHash(targetObject.getClass().getMethod(methodName, new Class[0]));
+      MethodInfo mi = new MethodInfo(targetObject.getClass(), hash, hash, null);
+      MethodInvocation invocation = new MethodInvocation(mi, new Interceptor[0]);
+      invocation.setTargetObject(targetObject);
+      return invocation;
+   }
+
+   // Inner classes --------------------------------------------------------------------------------
+
+   public interface BusinessObject
+   {
+      void businessMethod1();
+      void businessMethod2();
+      /**
+       * Must be unblocked externally.
+       */
+      void blockingBusinessMethod() throws InterruptedException;
+   }
+
+   public class SimpleInvocationTargetObject
+      extends DelegateSupport implements BusinessObject, Valve
+   {
+      private int businessMethod1InvocationCount;
+      private int businessMethod2InvocationCount;
+
+      // LinkedQueue<InvocationToken>
+      private LinkedQueue invocationTokens;
+
+      private FailoverValveInterceptor valve;
+      private int activeThreadsCountHighWaterMark;
+
+      private Object blockingMethodWaitArea;
+
+      public SimpleInvocationTargetObject(FailoverValveInterceptor valve)
+      {
+         super();
+         setState(new ParentHierarchicalState());
+         businessMethod1InvocationCount = 0;
+         businessMethod2InvocationCount = 0;
+         invocationTokens = new LinkedQueue();
+         this.valve = valve;
+         activeThreadsCountHighWaterMark = 0;
+         blockingMethodWaitArea = new Object();
+      }
+
+      protected Client getClient() throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public boolean isValveOpen()
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public void closeValve() throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public void openValve() throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public int getActiveThreadsCount()
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public synchronized void businessMethod1()
+      {
+         businessMethod1InvocationCount++;
+         updateActiveThreadsCountHighWaterMark();
+         notifyInvocationWaiter("businessMethod1");
+      }
+
+      public synchronized void businessMethod2()
+      {
+         businessMethod2InvocationCount++;
+         updateActiveThreadsCountHighWaterMark();
+         notifyInvocationWaiter("businessMethod2");
+      }
+
+      public void blockingBusinessMethod() throws InterruptedException
+      {
+         synchronized(blockingMethodWaitArea)
+         {
+            log.info("blockingBusinessMethod() blocking ...");
+            // block calling thread undefinitely until blockingMethodWaitArea is notified
+            blockingMethodWaitArea.wait();
+            log.info("blockingBusinessMethod() unblocked");
+         }
+      }
+
+      public synchronized int getBusinessMethod1InvocationCount()
+      {
+         return businessMethod1InvocationCount;
+      }
+
+      public synchronized int getBusinessMethod2InvocationCount()
+      {
+         return businessMethod2InvocationCount;
+      }
+
+      public synchronized int getActiveThreadsHighWaterMark()
+      {
+         return activeThreadsCountHighWaterMark;
+      }
+
+      /**
+       * Block until an invocation arrives into the target. If the invocation arrived prior to
+       * calling this method, it returns immediately.
+       *
+       * @return a token corresponding to the business invocation, or null if the method exited with
+       *         timout, without an invocation to arive.
+       */
+      public InvocationToken waitForInvocation(long timeout) throws InterruptedException
+      {
+         return (InvocationToken)invocationTokens.poll(timeout);
+      }
+
+      public void unblockBlockingBusinessMethod()
+      {
+         synchronized(blockingMethodWaitArea)
+         {
+            blockingMethodWaitArea.notify();
+         }
+      }
+
+      /**
+       * Reset the state
+       */
+      public synchronized void reset()
+      {
+         businessMethod1InvocationCount = 0;
+         businessMethod2InvocationCount = 0;
+         activeThreadsCountHighWaterMark = 0;
+      }
+
+      /**
+       * Notify someone who waits for this invocation to arrive.
+       */
+      private void notifyInvocationWaiter(String methodName)
+      {
+         try
+         {
+            invocationTokens.put(new InvocationToken(methodName));
+         }
+         catch(InterruptedException e)
+         {
+            throw new RuntimeException("Failed to deposit notification in queue", e);
+         }
+      }
+
+      private synchronized void updateActiveThreadsCountHighWaterMark()
+      {
+         try
+         {
+            int c =
+               ((Integer)valve.invoke(buildInvocation("getActiveThreadsCount", this))).intValue();
+            if (c > activeThreadsCountHighWaterMark)
+            {
+               activeThreadsCountHighWaterMark = c;
+            }
+         }
+         catch(Throwable t)
+         {
+            throw new RuntimeException("Failed to get getActiveThreadsCount", t);
+         }
+      }
+   }
+
+   private class ParentHierarchicalState extends HierarchicalStateSupport
+   {
+      private DelegateSupport delegate;
+      private HierarchicalState parent;
+
+      ParentHierarchicalState()
+      {
+         super(null, null);
+
+         children = new HashSet();
+
+         // populate it with a child state
+         children.add(new ChildState());
+      }
+
+      public DelegateSupport getDelegate()
+      {
+         return delegate;
+      }
+
+      public void setDelegate(DelegateSupport delegate)
+      {
+         this.delegate = delegate;
+      }
+
+      public HierarchicalState getParent()
+      {
+         return parent;
+      }
+
+      public void setParent(HierarchicalState parent)
+      {
+         this.parent = parent;
+      }
+
+      public Version getVersionToUse()
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public void synchronizeWith(HierarchicalState newState) throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+   }
+
+   private class ChildState extends HierarchicalStateSupport
+   {
+      private DelegateSupport delegate;
+      private HierarchicalState parent;
+
+      ChildState()
+      {
+         super(null, new ChildDelegate());
+      }
+
+      public Set getChildren()
+      {
+         return Collections.EMPTY_SET;
+      }
+
+      public DelegateSupport getDelegate()
+      {
+         return delegate;
+      }
+
+      public void setDelegate(DelegateSupport delegate)
+      {
+         this.delegate = delegate;
+      }
+
+      public HierarchicalState getParent()
+      {
+         return parent;
+      }
+
+      public void setParent(HierarchicalState parent)
+      {
+         this.parent = parent;
+      }
+
+      public Version getVersionToUse()
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public void synchronizeWith(HierarchicalState newState) throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+   }
+
+   private class ChildDelegate extends DelegateSupport implements Valve
+   {
+      private boolean valveOpen;
+
+      ChildDelegate()
+      {
+         valveOpen = true;
+      }
+
+      protected Client getClient() throws Exception
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+
+      public synchronized void closeValve() throws Exception
+      {
+         valveOpen = false;
+      }
+
+      public void openValve() throws Exception
+      {
+         valveOpen = true;
+      }
+
+      public boolean isValveOpen()
+      {
+         return valveOpen;
+      }
+
+      public int getActiveThreadsCount()
+      {
+         throw new RuntimeException("NOT YET IMPLEMENTED");
+      }
+   }
+
+   private class InvocationToken
+   {
+      private String methodName;
+
+      public InvocationToken(String methodName)
+      {
+         this.methodName = methodName;
+      }
+
+      public String getMethodName()
+      {
+         return methodName;
+      }
+   }
+
+}

Modified: trunk/tests/src/org/jboss/test/messaging/jms/clustering/FailoverTest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/clustering/FailoverTest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/clustering/FailoverTest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -9,16 +9,26 @@
 import org.jboss.test.messaging.jms.clustering.base.ClusteringTestBase;
 import org.jboss.test.messaging.tools.ServerManagement;
 import org.jboss.jms.client.JBossConnection;
+import org.jboss.jms.client.FailoverListener;
+import org.jboss.jms.client.FailoverEvent;
+import org.jboss.jms.client.Valve;
+import org.jboss.jms.client.JBossSession;
+import org.jboss.jms.client.JBossQueueBrowser;
+import org.jboss.jms.client.JBossMessageConsumer;
+import org.jboss.jms.client.JBossMessageProducer;
 import org.jboss.jms.client.state.ConnectionState;
 import org.jboss.jms.client.delegate.DelegateSupport;
 
 import javax.jms.Connection;
 import javax.jms.Session;
 import javax.jms.MessageProducer;
-import javax.jms.DeliveryMode;
 import javax.jms.MessageConsumer;
+import javax.jms.QueueBrowser;
 import javax.jms.TextMessage;
+import javax.jms.DeliveryMode;
 
+import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
+
 /**
  * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
  * @version <tt>$Revision$</tt>
@@ -27,75 +37,333 @@
  */
 public class FailoverTest extends ClusteringTestBase
 {
-   // Constants -----------------------------------------------------
+   // Constants ------------------------------------------------------------------------------------
 
-   // Static --------------------------------------------------------
+   // Static ---------------------------------------------------------------------------------------
 
-   // Attributes ----------------------------------------------------
+   // Attributes -----------------------------------------------------------------------------------
 
-   // Constructors --------------------------------------------------
+   // Constructors ---------------------------------------------------------------------------------
 
    public FailoverTest(String name)
    {
       super(name);
    }
 
-   // Public --------------------------------------------------------
+   // Public ---------------------------------------------------------------------------------------
 
-   public void testSimpleFailover() throws Exception
+   public void testSimpleConnectionFailover() throws Exception
    {
       Connection conn = null;
 
       try
       {
+         // skip connection to node 0
          conn = cf.createConnection();
          conn.close();
 
+         // create a connection to node 1
          conn = cf.createConnection();
          conn.start();
 
-         // create a producer/consumer on node 1
+         assertEquals(1, ((JBossConnection)conn).getServerID());
 
-         // make sure we're connecting to node 1
+         // register a failover listener
+         SimpleFailoverListener failoverListener = new SimpleFailoverListener();
+         ((JBossConnection)conn).registerFailoverListener(failoverListener);
 
+         log.debug("killing node 1 ....");
+
+         ServerManagement.kill(1);
+
+         log.info("########");
+         log.info("######## KILLED NODE 1");
+         log.info("########");
+
+         // wait for the client-side failover to complete
+
+         while(true)
+         {
+            FailoverEvent event = failoverListener.getEvent(120000);
+            if (event != null && FailoverEvent.FAILOVER_COMPLETED == event.getType())
+            {
+               break;
+            }
+            if (event == null)
+            {
+               fail("Did not get expected FAILOVER_COMPLETED event");
+            }
+         }
+
+         // failover complete
+         log.info("failover completed");
+
+         assertEquals(0, ((JBossConnection)conn).getServerID());
+
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+   }
+
+   public void testConnectionAndSessionFailover() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         // skip connection to node 0
+         conn = cf.createConnection();
+         conn.close();
+
+         // create a connection to node 1
+         conn = cf.createConnection();
+         conn.start();
+
+         assertEquals(1, ((JBossConnection)conn).getServerID());
+
+         Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         // register a failover listener
+         SimpleFailoverListener failoverListener = new SimpleFailoverListener();
+         ((JBossConnection)conn).registerFailoverListener(failoverListener);
+
+         log.debug("killing node 1 ....");
+
+         ServerManagement.kill(1);
+
+         log.info("########");
+         log.info("######## KILLED NODE 1");
+         log.info("########");
+
+         // wait for the client-side failover to complete
+
+         while(true)
+         {
+            FailoverEvent event = failoverListener.getEvent(120000);
+            if (event != null && FailoverEvent.FAILOVER_COMPLETED == event.getType())
+            {
+               break;
+            }
+            if (event == null)
+            {
+               fail("Did not get expected FAILOVER_COMPLETED event");
+            }
+         }
+
+         // failover complete
+         log.info("failover completed");
+
+         assertEquals(0, ((JBossConnection)conn).getServerID());
+
+         // use the old session to send/receive a message
+         session.createProducer(queue[0]).send(session.createTextMessage("blik"));
+
+         TextMessage m = (TextMessage)session.createConsumer(queue[0]).receive(2000);
+
+         assertEquals("blik", m.getText());
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+   }
+
+
+   public void testFailoverListener() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         conn = cf.createConnection();
+         conn.close();
+
+         conn = cf.createConnection();
+         conn.start();
+
+         // create a producer/consumer on node 1 and make sure we're connecting to node 1
+
          int nodeID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn).
             getDelegate()).getState()).getServerID();
 
          assertEquals(1, nodeID);
 
-         Session s1 = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
-         MessageConsumer c1 = s1.createConsumer(queue[1]);
-         MessageProducer p1 = s1.createProducer(queue[1]);
-         p1.setDeliveryMode(DeliveryMode.PERSISTENT);
+         SimpleFailoverListener failoverListener = new SimpleFailoverListener();
+         ((JBossConnection)conn).registerFailoverListener(failoverListener);
 
+         // kill node 1
+
+         log.debug("killing node 1");
+
+         ServerManagement.kill(1);
+
+         log.info("########");
+         log.info("######## KILLED NODE 1");
+         log.info("########");
+
+         FailoverEvent event = failoverListener.getEvent(120000);
+
+         assertNotNull(event);
+         assertEquals(FailoverEvent.FAILURE_DETECTED, event.getType());
+         log.info("got " + event);
+
+         event = failoverListener.getEvent(120000);
+
+         assertNotNull(event);
+         assertEquals(FailoverEvent.FAILOVER_STARTED, event.getType());
+         log.info("got " + event);
+
+         event = failoverListener.getEvent(120000);
+
+         assertNotNull(event);
+         assertEquals(FailoverEvent.FAILOVER_COMPLETED, event.getType());
+         log.info("got " + event);
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+   }
+
+   public void testCloseValveHierarchy() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         conn = cf.createConnection();
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+
+         Session session1 = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         Session session2 = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         assertTrue(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+
+         MessageProducer prod1 = session1.createProducer(queue[0]);
+         assertTrue(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+
+         MessageConsumer cons1 = session1.createConsumer(queue[0]);
+         assertTrue(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+
+         QueueBrowser browser1 = session1.createBrowser(queue[0]);
+         assertTrue(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+
+         MessageProducer prod2 = session2.createProducer(queue[0]);
+         assertTrue(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+
+         MessageConsumer cons2 = session2.createConsumer(queue[0]);
+         assertTrue(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+
+         QueueBrowser browser2 = session2.createBrowser(queue[0]);
+         assertTrue(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().closeValve();
+
+         log.debug("top level valve closed");
+
+         assertFalse(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+         assertFalse(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+
+         ((JBossConnection)conn).getDelegate().openValve();
+
+         log.debug("top level valve open");
+
+         assertTrue(((Valve)((JBossConnection)conn).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossSession)session2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageProducer)prod1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageConsumer)cons1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossQueueBrowser)browser1).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageProducer)prod2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossMessageConsumer)cons2).getDelegate()).isValveOpen());
+         assertTrue(((Valve)((JBossQueueBrowser)browser2).getDelegate()).isValveOpen());
+      }
+      finally
+      {
+         if (conn != null)
+         {
+            conn.close();
+         }
+      }
+   }
+
+   public void testFailoverMessageOnServer() throws Exception
+   {
+      Connection conn = null;
+
+      try
+      {
+         conn = cf.createConnection();
+         conn.close();
+
+         conn = cf.createConnection();
+         conn.start();
+
+         assertEquals(1, ((JBossConnection)conn).getServerID());
+
+         SimpleFailoverListener listener = new SimpleFailoverListener();
+         ((JBossConnection)conn).registerFailoverListener(listener);
+
+         Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         MessageProducer prod = session.createProducer(queue[1]);
+         prod.setDeliveryMode(DeliveryMode.PERSISTENT);
+         MessageConsumer cons = session.createConsumer(queue[0]);
+
+
          // send a message
 
-         p1.send(s1.createTextMessage("blip"));
+         prod.send(session.createTextMessage("blip"));
 
          // kill node 1
 
+         log.debug("killing node 1");
 
+         ServerManagement.kill(1);
 
-         ServerManagement.killAndWait(1);
-
          log.info("########");
          log.info("######## KILLED NODE 1");
          log.info("########");
 
-         try
+         // wait until the failure (not the completion of client-side failover) is detected
+
+         while(true)
          {
-            ic[1].lookup("queue"); // looking up anything
-            fail("The server still alive, kill didn't work yet");
+            FailoverEvent event = listener.getEvent(120000);
+            if (event != null && FailoverEvent.FAILOVER_STARTED == event.getType())
+            {
+               break;
+            }
+            if (event == null)
+            {
+               fail("Did not get expected FAILOVER_COMPLETED event");
+            }
          }
-         catch (Exception e)
-         {
-         }
 
-         // we must receive the message
+         // start to receive the very next moment the failure is detected. This way, we also
+         // test the client-side failover valve
 
-         TextMessage tm = (TextMessage)c1.receive(1000);
+         TextMessage tm = (TextMessage)cons.receive(60000);
          assertEquals("blip", tm.getText());
-
       }
       finally
       {
@@ -106,10 +374,69 @@
       }
    }
 
-   // Package protected ---------------------------------------------
+   // TODO http://jira.jboss.org/jira/browse/JBMESSAGING-712
+//   public void testFailoverMessageOnServer2() throws Exception
+//   {
+//      Connection conn = null;
+//
+//      try
+//      {
+//         conn = cf.createConnection();
+//         conn.close();
+//
+//         conn = cf.createConnection();
+//         conn.start();
+//
+//         assertEquals(1, ((JBossConnection)conn).getServerID());
+//
+//         SimpleFailoverListener listener = new SimpleFailoverListener();
+//         ((JBossConnection)conn).registerFailoverListener(listener);
+//
+//         Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+//         MessageProducer prod = session.createProducer(queue[1]);
+//         prod.setDeliveryMode(DeliveryMode.PERSISTENT);
+//
+//         // send a message
+//
+//         prod.send(session.createTextMessage("blip"));
+//
+//         // kill node 1
+//
+//         log.debug("killing node 1");
+//
+//         ServerManagement.kill(1);
+//
+//         log.info("########");
+//         log.info("######## KILLED NODE 1");
+//         log.info("########");
+//
+//         // wait until the failure (not the completion of client-side failover) is detected
+//
+//         assertEquals(FailoverEvent.FAILURE_DETECTED, listener.getEvent(60000).getType());
+//
+//         // create a consumer the very next moment the failure is detected. This way, we also
+//         // test the client-side failover valve
+//
+//         MessageConsumer cons = session.createConsumer(queue[0]);
+//
+//         // we must receive the message
+//
+//         TextMessage tm = (TextMessage)cons.receive(60000);
+//         assertEquals("blip", tm.getText());
+//      }
+//      finally
+//      {
+//         if (conn != null)
+//         {
+//            conn.close();
+//         }
+//      }
+//   }
 
-   // Protected -----------------------------------------------------
+   // Package protected ----------------------------------------------------------------------------
 
+   // Protected ------------------------------------------------------------------------------------
+
    protected void setUp() throws Exception
    {
       nodeCount = 2;
@@ -124,8 +451,39 @@
       super.tearDown();
    }
 
-   // Private -------------------------------------------------------
+   // Private --------------------------------------------------------------------------------------
 
-   // Inner classes -------------------------------------------------
+   // Inner classes --------------------------------------------------------------------------------
 
+   private class SimpleFailoverListener implements FailoverListener
+   {
+      private LinkedQueue buffer;
+
+      public SimpleFailoverListener()
+      {
+         buffer = new LinkedQueue();
+      }
+
+      public void failoverEventOccured(FailoverEvent event)
+      {
+         try
+         {
+            buffer.put(event);
+         }
+         catch(InterruptedException e)
+         {
+            throw new RuntimeException("Putting thread interrupted while trying to add event " +
+               "to buffer", e);
+         }
+      }
+
+      /**
+       * Blocks until a FailoverEvent is available or timeout occurs, in which case returns null.
+       */
+      public FailoverEvent getEvent(long timeout) throws InterruptedException
+      {
+         return (FailoverEvent)buffer.poll(timeout);
+      }
+   }
+
 }

Modified: trunk/tests/src/org/jboss/test/messaging/jms/clustering/HAStressTest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/clustering/HAStressTest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/clustering/HAStressTest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -41,8 +41,9 @@
 /**
  * @author <a href="mailto:clebert.suconic at jboss.org">Clebert Suconic</a>
  * @version <tt>$Revision:$</tt>
- *          <p/>
- *          $Id:$
+ *
+ * 
+ * $Id:$
  */
 public class HAStressTest extends ClusteringTestBase
 {

Modified: trunk/tests/src/org/jboss/test/messaging/jms/clustering/HATest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/clustering/HATest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/clustering/HATest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -41,7 +41,7 @@
 import org.jboss.jms.client.remoting.JMSRemotingConnection;
 import org.jboss.jms.client.delegate.ClientConnectionDelegate;
 import org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate;
-import org.jboss.jms.client.delegate.ClusteredClientConnectionFactoryDelegate;
+import org.jboss.jms.client.delegate.ClientClusteredConnectionFactoryDelegate;
 import org.jboss.jms.client.delegate.DelegateSupport;
 import org.jboss.jms.client.delegate.ClientSessionDelegate;
 import org.jboss.jms.client.state.ConnectionState;
@@ -156,8 +156,8 @@
    {
       JBossConnectionFactory factory =  (JBossConnectionFactory )ic[0].lookup("/ConnectionFactory");
 
-      ClusteredClientConnectionFactoryDelegate delegate =
-         (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+      ClientClusteredConnectionFactoryDelegate delegate =
+         (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
       log.info ("number of delegates = " + delegate.getDelegates().length);
       log.info ("number of servers = " + ServerManagement.getServer(0).getNodeIDView().size());
@@ -368,8 +368,8 @@
       {
          JBossConnectionFactory factory =  (JBossConnectionFactory )ic[0].lookup("/ConnectionFactory");
 
-         ClusteredClientConnectionFactoryDelegate delegate =
-            (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+         ClientClusteredConnectionFactoryDelegate delegate =
+            (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
          assertEquals(3, ServerManagement.getServer(0).getNodeIDView().size());
 
@@ -429,8 +429,8 @@
 
          log.info("Got connection factory");
 
-         ClusteredClientConnectionFactoryDelegate delegate =
-            (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+         ClientClusteredConnectionFactoryDelegate delegate =
+            (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
          ClientConnectionFactoryDelegate[] delegates = delegate.getDelegates();
 
@@ -475,8 +475,8 @@
 
          JBossConnectionFactory factory =  (JBossConnectionFactory )ic[2].lookup("/ConnectionFactory");
 
-         ClusteredClientConnectionFactoryDelegate delegate =
-            (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+         ClientClusteredConnectionFactoryDelegate delegate =
+            (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
          ClientConnectionFactoryDelegate[] delegates = delegate.getDelegates();
 
@@ -503,8 +503,8 @@
 
          log.info("Got connection factory");
 
-         ClusteredClientConnectionFactoryDelegate delegate =
-            (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+         ClientClusteredConnectionFactoryDelegate delegate =
+            (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
          ClientConnectionFactoryDelegate[] delegates = delegate.getDelegates();
 
@@ -544,8 +544,8 @@
 
          log.info("Got connection factory");
 
-         ClusteredClientConnectionFactoryDelegate delegate =
-            (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+         ClientClusteredConnectionFactoryDelegate delegate =
+            (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
          ClientConnectionFactoryDelegate[] delegates = delegate.getDelegates();
 
@@ -588,8 +588,8 @@
    {
       JBossConnectionFactory factory =  (JBossConnectionFactory )ic[0].lookup("/ConnectionFactory");
 
-      ClusteredClientConnectionFactoryDelegate delegate =
-         (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+      ClientClusteredConnectionFactoryDelegate delegate =
+         (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
       Set nodeIDView = ServerManagement.getServer(0).getNodeIDView();
       assertEquals(3, nodeIDView.size());
@@ -726,8 +726,8 @@
    {
       JBossConnectionFactory factory =  (JBossConnectionFactory )ic[0].lookup("/ConnectionFactory");
 
-      ClusteredClientConnectionFactoryDelegate delegate =
-         (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+      ClientClusteredConnectionFactoryDelegate delegate =
+         (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
       Set nodeIDView = ServerManagement.getServer(0).getNodeIDView();
       assertEquals(3, nodeIDView.size());
@@ -919,8 +919,8 @@
    {
       JBossConnectionFactory factory =  (JBossConnectionFactory )ic[0].lookup("/ConnectionFactory");
 
-      ClusteredClientConnectionFactoryDelegate delegate =
-         (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+      ClientClusteredConnectionFactoryDelegate delegate =
+         (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 
       Set nodeIDView = ServerManagement.getServer(0).getNodeIDView();
       assertEquals(3, nodeIDView.size());
@@ -1189,8 +1189,8 @@
 // try
 // {
 // JBossConnectionFactory factory =  (JBossConnectionFactory )ic[2].lookup("/ConnectionFactory");
-// ClusteredClientConnectionFactoryDelegate delegate =
-// (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+// ClientClusteredConnectionFactoryDelegate delegate =
+// (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 // log.info ("number of delegates = " + delegate.getDelegates().length);
 // log.info ("number of servers = " + ServerManagement.getServer(0).getNumberOfNodesOnCluster());
 // 
@@ -1218,7 +1218,7 @@
 // ServerManagement.log(ServerManagement.INFO,"##### Looking up ConnectionFactory at testConnectionFactoryConnect");
 // 
 // factory =  (JBossConnectionFactory )ic[2].lookup("/ConnectionFactory");
-// delegate = (ClusteredClientConnectionFactoryDelegate)factory.getDelegate();
+// delegate = (ClientClusteredConnectionFactoryDelegate)factory.getDelegate();
 // 
 // assertEquals(2, ServerManagement.getServer(0).getNumberOfNodesOnCluster());
 // assertEquals(2, delegate.getDelegates().length);
@@ -1240,7 +1240,7 @@
 // 
 // 
 // JBossConnectionFactory jbcf1 = (JBossConnectionFactory)cf;
-// assertTrue(jbcf1.getDelegate() instanceof ClusteredClientConnectionFactoryDelegate);
+// assertTrue(jbcf1.getDelegate() instanceof ClientClusteredConnectionFactoryDelegate);
 // 
 // log.info(">>Lookup Queue");
 // Destination destination = (Destination) ic[2].lookup("topic/testDistributedTopic");

Added: trunk/tests/src/org/jboss/test/messaging/jms/clustering/LoadBalancingTest.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/clustering/LoadBalancingTest.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/clustering/LoadBalancingTest.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -0,0 +1,175 @@
+/**
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.test.messaging.jms.clustering;
+
+import org.jboss.test.messaging.tools.ServerManagement;
+import org.jboss.test.messaging.MessagingTestCase;
+import org.jboss.jms.client.JBossConnection;
+import org.jboss.jms.client.state.ConnectionState;
+import org.jboss.jms.client.delegate.DelegateSupport;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.naming.InitialContext;
+
+/**
+ * This test DOESN'T extend ClusteringTestBase because I want to have control over first invocations
+ * to the server (which are outside of of my control if I use ClusteringTestBase).
+ *
+ * @author <a href="mailto:ovidiu at jboss.org">Ovidiu Feodorov</a>
+ * @version <tt>$Revision: 1.1 $</tt>
+ *
+ * $Id$
+ */
+public class LoadBalancingTest extends MessagingTestCase
+{
+
+   // Constants -----------------------------------------------------
+
+   // Static --------------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   // Constructors --------------------------------------------------
+
+   public LoadBalancingTest(String name)
+   {
+      super(name);
+   }
+
+   // Public --------------------------------------------------------
+
+   public void testLoadBalancingOneNode() throws Exception
+   {
+      ServerManagement.start(0, "all", true);
+
+      try
+      {
+         InitialContext ic = new InitialContext(ServerManagement.getJNDIEnvironment(0));
+
+         ConnectionFactory cf = (ConnectionFactory)ic.lookup("/ConnectionFactory");
+
+         Connection conn0 = cf.createConnection();
+
+         int serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn0).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+         Connection conn1 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn1).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+         Connection conn2 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn2).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+         conn0.close();
+         conn1.close();
+         conn2.close();
+
+         ic.close();
+      }
+      finally
+      {
+         ServerManagement.stop(0);
+      }
+   }
+
+   public void testLoadBalancingTwoNodes() throws Exception
+   {
+      // Make sure all servers are created and started; make sure that database is zapped ONLY for
+      // the first server, the others rely on values they expect to find in shared tables; don't
+      // clear the database for those.
+
+      ServerManagement.start(0, "all", true);
+      ServerManagement.start(1, "all", false);
+
+
+      try
+      {
+         InitialContext ic0 = new InitialContext(ServerManagement.getJNDIEnvironment(0));
+
+         ConnectionFactory cf = (ConnectionFactory)ic0.lookup("/ConnectionFactory");
+
+         Connection conn0 = cf.createConnection();
+
+         int serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn0).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+         Connection conn1 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn1).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(1, serverID);
+
+         Connection conn2 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn2).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+         Connection conn3 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn3).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(1, serverID);
+
+         Connection conn4 = cf.createConnection();
+
+         serverID = ((ConnectionState)((DelegateSupport)((JBossConnection)conn4).getDelegate()).
+            getState()).getServerID();
+
+         assertEquals(0, serverID);
+
+
+         conn0.close();
+         conn1.close();
+         conn2.close();
+         conn3.close();
+         conn4.close();
+
+         ic0.close();
+      }
+      finally
+      {
+         ServerManagement.stop(1);
+         ServerManagement.stop(0);
+      }
+   }
+
+   // Package protected ---------------------------------------------
+
+   // Protected -----------------------------------------------------
+
+   protected void setUp() throws Exception
+   {
+      super.setUp();
+      log.debug("setup done");
+   }
+
+   protected void tearDown() throws Exception
+   {
+      super.tearDown();
+   }
+
+   // Private -------------------------------------------------------
+
+   // Inner classes -------------------------------------------------
+
+}

Modified: trunk/tests/src/org/jboss/test/messaging/jms/clustering/base/ClusteringTestBase.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/jms/clustering/base/ClusteringTestBase.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/jms/clustering/base/ClusteringTestBase.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -132,7 +132,8 @@
 
    protected String getLocatorURL(Connection conn)
    {
-      return getConnectionState(conn).getRemotingConnection().getInvokingClient().getInvoker().getLocator().getLocatorURI();
+      return getConnectionState(conn).getRemotingConnection().
+         getInvokingClient().getInvoker().getLocator().getLocatorURI();
    }
 
    protected int getServerId(Connection conn)
@@ -152,7 +153,8 @@
          getDelegate()).getState());
    }
 
-   protected Connection createConnectionOnServer(ConnectionFactory factory, int serverId) throws Exception
+   protected Connection createConnectionOnServer(ConnectionFactory factory, int serverId)
+      throws Exception
    {
       int count=0;
 

Modified: trunk/tests/src/org/jboss/test/messaging/tools/ServerManagement.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/tools/ServerManagement.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/tools/ServerManagement.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -275,7 +275,7 @@
          while(true)
          {
             server.ping();
-            log.info("Server still alive... pinging again");
+            log.debug("Server " + i + " still alive... pinging again");
             Thread.sleep(10);
          }
       }

Modified: trunk/tests/src/org/jboss/test/messaging/tools/jmx/ServiceContainer.java
===================================================================
--- trunk/tests/src/org/jboss/test/messaging/tools/jmx/ServiceContainer.java	2006-12-31 09:49:46 UTC (rev 1879)
+++ trunk/tests/src/org/jboss/test/messaging/tools/jmx/ServiceContainer.java	2006-12-31 10:51:38 UTC (rev 1880)
@@ -510,7 +510,7 @@
       String persistenceConfigFile =
          "server/default/deploy/" + databaseType + "-persistence-service.xml";
 
-      log.info("********* Using config file: " + persistenceConfigFile);
+      log.info("Peristence config file: .. " + persistenceConfigFile);
 
       URL persistenceConfigFileURL = getClass().getClassLoader().getResource(persistenceConfigFile);
       if (persistenceConfigFileURL == null)
@@ -548,7 +548,7 @@
       String persistenceConfigFile =
          "server/default/deploy/" + databaseType + "-persistence-service.xml";
 
-      log.info("********* Using config file: " + persistenceConfigFile);
+      log.info("Peristence config file: .. " + persistenceConfigFile);
 
       URL persistenceConfigFileURL = getClass().getClassLoader().getResource(persistenceConfigFile);
       if (persistenceConfigFileURL == null)
@@ -595,7 +595,7 @@
             "server/default/deploy/clustered-" + databaseType + "-persistence-service.xml";
       }
 
-      log.info("********* Using config file: " + persistenceConfigFile);
+      log.info("Peristence config file: .. " + persistenceConfigFile);
 
       URL persistenceConfigFileURL = getClass().getClassLoader().getResource(persistenceConfigFile);
       if (persistenceConfigFileURL == null)




More information about the jboss-cvs-commits mailing list