[jboss-cvs] JBossAS SVN: r79611 - in trunk: testsuite/src/main/org/jboss/test/cluster/web and 5 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Thu Oct 16 19:12:00 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-10-16 19:12:00 -0400 (Thu, 16 Oct 2008)
New Revision: 79611

Added:
   trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/ClusteredSessionNotificationPolicyTestCase.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/InvalidateSessionRequestHandler.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/RemoveAttributesRequestHandler.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/
   trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockClusteredSessionNotificationPolicy.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionAttributeListener.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionListener.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/SessionSpecListenerAttribute.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionManagementStatus.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCapability.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCause.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicy.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicyBase.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/IgnoreUndeployLegacyClusteredSessionNotificationPolicy.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/LegacyClusteredSessionNotificationPolicy.java
Modified:
   trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
Log:
[JBAS-5778] Improve servlet spec notification handling in clustered environment

Added: trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/ClusteredSessionNotificationPolicyTestCase.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/ClusteredSessionNotificationPolicyTestCase.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/ClusteredSessionNotificationPolicyTestCase.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,704 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.defaultcfg.simpleweb.test;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.Test;
+
+import org.apache.catalina.Context;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.jboss.ReplicationGranularity;
+import org.jboss.metadata.web.jboss.ReplicationTrigger;
+import org.jboss.test.JBossTestCase;
+import org.jboss.test.cluster.testutil.CacheConfigTestSetup;
+import org.jboss.test.cluster.testutil.SessionTestUtil;
+import org.jboss.test.cluster.web.mocks.BasicRequestHandler;
+import org.jboss.test.cluster.web.mocks.InvalidateSessionRequestHandler;
+import org.jboss.test.cluster.web.mocks.RemoveAttributesRequestHandler;
+import org.jboss.test.cluster.web.mocks.SetAttributesRequestHandler;
+import org.jboss.test.cluster.web.notification.MockClusteredSessionNotificationPolicy;
+import org.jboss.test.cluster.web.notification.MockHttpSessionAttributeListener;
+import org.jboss.test.cluster.web.notification.MockHttpSessionListener;
+import org.jboss.test.cluster.web.notification.SessionSpecListenerAttribute;
+import org.jboss.web.tomcat.service.session.JBossCacheManager;
+
+/**
+ * Tests of handling of servlet spec notifications. 
+ * 
+ * @author Brian Stansberry
+ */
+public class ClusteredSessionNotificationPolicyTestCase extends JBossTestCase
+{
+   protected static PojoCache[] pojoCaches = new PojoCache[2];
+
+   protected static long testId = System.currentTimeMillis();
+   
+   protected static boolean useBuddyRepl = Boolean.valueOf(System.getProperty("jbosstest.cluster.web.cache.br")).booleanValue();
+   
+   protected Logger log = Logger.getLogger(getClass());   
+   
+   protected Set<JBossCacheManager> managers = new HashSet<JBossCacheManager>();
+   
+   protected Map<String, Object> allAttributes;
+   protected Map<String, Object> immutables;
+   protected Map<String, Object> mutables;
+   protected Map<String, Object> attributes;
+   protected SessionSpecListenerAttribute attribute = new SessionSpecListenerAttribute();
+   protected Map<String, Object> newAttributes;
+   protected SessionSpecListenerAttribute newAttribute = new SessionSpecListenerAttribute();
+   
+   protected String origNotificationPolicy;
+   
+   public ClusteredSessionNotificationPolicyTestCase(String name)
+   {
+      super(name);
+   }
+   
+   public static Test suite() throws Exception
+   {
+      File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+      File root = new File(tmpDir, ClusteredSessionNotificationPolicyTestCase.class.getSimpleName());
+      return CacheConfigTestSetup.getTestSetup(ClusteredSessionNotificationPolicyTestCase.class, pojoCaches, false, root.getAbsolutePath(), !useBuddyRepl, false);
+   }
+
+   
+   @Override
+   protected void setUp() throws Exception
+   {
+      super.setUp();
+      
+      origNotificationPolicy = System.getProperty("jboss.web.clustered.session.notification.policy");
+      System.setProperty("jboss.web.clustered.session.notification.policy", MockClusteredSessionNotificationPolicy.class.getName());
+      
+      attributes = new HashMap<String, Object>();
+      attributes.put("KEY", attribute);
+      attributes = Collections.unmodifiableMap(attributes);
+      
+      newAttributes = new HashMap<String, Object>();
+      newAttributes.put("KEY", newAttribute);
+      newAttributes = Collections.unmodifiableMap(newAttributes);
+   }
+
+   @Override
+   protected void tearDown() throws Exception
+   {
+      super.tearDown();
+      
+      if (origNotificationPolicy != null)
+      {
+         System.setProperty("jboss.web.clustered.session.notification.policy", origNotificationPolicy);
+      }
+      else
+      {
+         System.clearProperty("jboss.web.clustered.session.notification.policy");
+      }
+      
+      for (JBossCacheManager manager : managers)    
+      {
+         try
+         {
+            manager.stop();
+         }
+         catch (RuntimeException ignored)
+         {
+            log.debug("tearDown(): Caught exception cleaning up manager -- " + ignored.getLocalizedMessage()); 
+         }
+      }
+      managers.clear();
+      
+      attribute.invocations.clear();
+      newAttribute.invocations.clear();
+   }
+   
+   protected ReplicationGranularity getReplicationGranularity()
+   {
+      return ReplicationGranularity.SESSION;
+   }
+   
+   protected ReplicationTrigger getReplicationTrigger()
+   {
+      return ReplicationTrigger.SET_AND_NON_PRIMITIVE_GET;
+   }
+   
+   public void testSessionLifecycleWithNotifications() throws Exception
+   {
+      log.info("++++ Starting testSessionLifecycleWithNotifications ++++");
+      sessionLifecycleTest(true);
+   }
+   
+   public void testSessionLifecycleWithoutNotifications() throws Exception
+   {
+      log.info("++++ Starting testSessionLifecycleWithoutNotifications ++++");
+      sessionLifecycleTest(false);
+   }
+   
+   private void sessionLifecycleTest(boolean notify) throws Exception
+   {
+      String warname = String.valueOf(++testId);
+      
+      // A war with a maxInactive of 30 mins maxUnreplicated of 0
+      JBossCacheManager[] mgrs = getCacheManagers(warname, 1800, 1);
+      JBossCacheManager jbcm0 = mgrs[0];
+      JBossCacheManager jbcm1 = mgrs[1];
+      
+      assertTrue(jbcm0.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp0 = (MockClusteredSessionNotificationPolicy) jbcm0.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp0.getClusteredSessionNotificationCapability());
+      mcsnp0.setResponse(notify);
+      
+      assertTrue(jbcm1.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp1 = (MockClusteredSessionNotificationPolicy) jbcm1.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp1.getClusteredSessionNotificationCapability());
+      mcsnp1.setResponse(notify);
+      
+      MockHttpSessionListener hsl0 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal0 = new MockHttpSessionAttributeListener();      
+      Context ctx = (Context) jbcm0.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl0 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal0 });  
+      
+      MockHttpSessionListener hsl1 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal1 = new MockHttpSessionAttributeListener();      
+      ctx = (Context) jbcm1.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl1 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal1 }); 
+      
+      // Initial request
+      SetAttributesRequestHandler setHandler = new SetAttributesRequestHandler(attributes, false);
+      SessionTestUtil.invokeRequest(jbcm0, setHandler, null);
+      
+      validateNewSession(setHandler);
+      String sessionId = setHandler.getSessionId();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.CREATED, hsl0.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.ADDED, hsal0.invocations.get(0));
+         assertEquals(2, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(1));
+         
+         validateNoNotifications(null, null, hsl1, hsal1, null);
+         clearNotifications(hsl0, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Modify attribute request
+      setHandler = new SetAttributesRequestHandler(newAttributes, false);
+      SessionTestUtil.invokeRequest(jbcm0, setHandler, sessionId);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REPLACED, hsal0.invocations.get(0));
+         assertEquals(4, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(1));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(2));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(3));
+         
+         validateNoNotifications(hsl0, null, hsl1, hsal1, null);
+         clearNotifications(null, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Passivate
+      Thread.sleep(1100);
+      
+      jbcm0.backgroundProcess();
+      jbcm1.backgroundProcess();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(0));
+         
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1, null);
+         clearNotifications(null, null, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Remove attribute request
+      RemoveAttributesRequestHandler removeHandler = new RemoveAttributesRequestHandler(newAttributes.keySet(), false);
+      SessionTestUtil.invokeRequest(jbcm0, removeHandler, sessionId);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REMOVED, hsal0.invocations.get(0));
+         assertEquals(3, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(1));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(2));
+         
+         validateNoNotifications(hsl0, null, hsl1, hsal1, null);
+         clearNotifications(null, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Failover request
+      setHandler = new SetAttributesRequestHandler(attributes, false);
+      SessionTestUtil.invokeRequest(jbcm1, setHandler, sessionId);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl1.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.CREATED, hsl1.invocations.get(0));
+         assertEquals(1, hsal1.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.ADDED, hsal1.invocations.get(0));
+         assertEquals(2, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(1));
+         
+         validateNoNotifications(hsl0, hsal0, null, null, null);
+         clearNotifications(null, null, hsl1, hsal1, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Passivate
+      Thread.sleep(1100);
+      
+      jbcm0.backgroundProcess();
+      jbcm1.backgroundProcess();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(0));
+         
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1, null);
+         clearNotifications(null, null, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Fail back and invalidate session after changing attribute
+      InvalidateSessionRequestHandler invalidateHandler = new InvalidateSessionRequestHandler(newAttributes.keySet(), false);
+      SessionTestUtil.invokeRequest(jbcm0, invalidateHandler, sessionId);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.DESTROYED, hsl0.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REMOVED, hsal0.invocations.get(0));
+         assertEquals(3, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(1));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(2));
+         
+         validateNoNotifications(null, null, hsl1, hsal1, null);
+         clearNotifications(hsl0, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+      }
+   }
+   
+   public void testSessionExpirationWithNotifications() throws Exception
+   {
+      log.info("++++ Starting testSessionExpirationWithNotifications ++++");
+      sessionExpirationTest(true);
+   }
+   
+   public void testSessionExpirationWithoutNotifications() throws Exception
+   {
+      log.info("++++ Starting testSessionExpirationWithoutNotifications ++++");
+      sessionExpirationTest(false);
+   }
+   
+   private void sessionExpirationTest(boolean notify) throws Exception
+   {
+      String warname = String.valueOf(++testId);
+      
+      // A war with a maxInactive of 2 secs and a maxIdle of 1
+      JBossCacheManager[] mgrs = getCacheManagers(warname, 2, 1);
+      JBossCacheManager jbcm0 = mgrs[0];
+      JBossCacheManager jbcm1 = mgrs[1];
+      
+      assertTrue(jbcm0.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp0 = (MockClusteredSessionNotificationPolicy) jbcm0.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp0.getClusteredSessionNotificationCapability());
+      mcsnp0.setResponse(notify);
+      
+      assertTrue(jbcm1.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp1 = (MockClusteredSessionNotificationPolicy) jbcm1.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp1.getClusteredSessionNotificationCapability());
+      mcsnp1.setResponse(notify);
+      
+      MockHttpSessionListener hsl0 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal0 = new MockHttpSessionAttributeListener();      
+      Context ctx = (Context) jbcm0.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl0 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal0 }); 
+      
+      MockHttpSessionListener hsl1 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal1 = new MockHttpSessionAttributeListener();      
+      ctx = (Context) jbcm1.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl1 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal1 }); 
+      
+      // Initial request
+      SetAttributesRequestHandler setHandler = new SetAttributesRequestHandler(attributes, false);
+      SessionTestUtil.invokeRequest(jbcm0, setHandler, null);
+      
+      validateNewSession(setHandler);
+      
+      String sessionId = setHandler.getSessionId();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.CREATED, hsl0.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.ADDED, hsal0.invocations.get(0));
+         assertEquals(2, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(1));
+         
+         validateNoNotifications(null, null, hsl1, hsal1, null);
+         clearNotifications(hsl0, hsal0, null, null, SessionSpecListenerAttribute.invocations);         
+      }
+      
+      // Failover request
+      setHandler = new SetAttributesRequestHandler(newAttributes, false);
+      SessionTestUtil.invokeRequest(jbcm1, setHandler, sessionId);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl1.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.CREATED, hsl1.invocations.get(0));
+         assertEquals(1, hsal1.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REPLACED, hsal1.invocations.get(0));
+         assertEquals(4, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(1));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(2));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(3));
+         
+         validateNoNotifications(hsl0, hsal0, null, null, null);
+         clearNotifications(null, null, hsl1, hsal1, SessionSpecListenerAttribute.invocations);         
+      }
+      
+      // Passivate
+      Thread.sleep(1100);
+      
+      jbcm0.backgroundProcess();
+      jbcm1.backgroundProcess();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(2, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(1));
+         
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1, null);
+         clearNotifications(null, null, null, null, SessionSpecListenerAttribute.invocations);
+      }
+      
+      // Expire
+      Thread.sleep(1000);
+      
+      jbcm0.backgroundProcess();
+      jbcm1.backgroundProcess();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.DESTROYED, hsl0.invocations.get(0));
+         assertEquals(1, hsl1.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.DESTROYED, hsl1.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REMOVED, hsal0.invocations.get(0));
+         assertEquals(1, hsal1.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REMOVED, hsal1.invocations.get(0));
+         assertEquals(4, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(1));
+         assertEquals(SessionSpecListenerAttribute.Type.ACTIVATING, SessionSpecListenerAttribute.invocations.get(2));
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(3));
+         
+         validateNoNotifications(null, null, null, null, null);
+         clearNotifications(hsl0, hsal0, hsl1, hsal1, SessionSpecListenerAttribute.invocations);
+      }
+   }
+   
+   public void testUndeployWithNotifications() throws Exception
+   {
+      log.info("++++ Starting testUndeployWithNotifications ++++");
+      undeployTest(true);
+   }
+   
+   public void testUndeployWithoutNotifications() throws Exception
+   {
+      log.info("++++ Starting testUndeployWithoutNotifications ++++");
+      undeployTest(false);
+   }
+   
+   private void undeployTest(boolean notify) throws Exception
+   {
+      String warname = String.valueOf(++testId);
+      
+      // A war with a maxInactive of 30 mins and no maxIdle
+      JBossCacheManager[] mgrs = getCacheManagers(warname, 1800, -1);
+      JBossCacheManager jbcm0 = mgrs[0];
+      JBossCacheManager jbcm1 = mgrs[1];
+      
+      assertTrue(jbcm0.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp0 = (MockClusteredSessionNotificationPolicy) jbcm0.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp0.getClusteredSessionNotificationCapability());
+      mcsnp0.setResponse(notify);
+      
+      assertTrue(jbcm1.getNotificationPolicy() instanceof MockClusteredSessionNotificationPolicy);
+      MockClusteredSessionNotificationPolicy mcsnp1 = (MockClusteredSessionNotificationPolicy) jbcm1.getNotificationPolicy();
+      assertNotNull("capability set", mcsnp1.getClusteredSessionNotificationCapability());
+      mcsnp1.setResponse(notify);
+      
+      MockHttpSessionListener hsl0 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal0 = new MockHttpSessionAttributeListener();      
+      Context ctx = (Context) jbcm0.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl0 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal0 });  
+      
+      MockHttpSessionListener hsl1 = new MockHttpSessionListener();
+      MockHttpSessionAttributeListener hsal1 = new MockHttpSessionAttributeListener();      
+      ctx = (Context) jbcm1.getContainer();
+      ctx.setApplicationLifecycleListeners(new Object[]{ hsl1 });  
+      ctx.setApplicationEventListeners(new Object[]{ hsal1 }); 
+      
+      // Initial request
+      SetAttributesRequestHandler setHandler = new SetAttributesRequestHandler(attributes, false);
+      SessionTestUtil.invokeRequest(jbcm0, setHandler, null);
+      
+      validateNewSession(setHandler);
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.CREATED, hsl0.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.ADDED, hsal0.invocations.get(0));
+         assertEquals(2, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.BOUND, SessionSpecListenerAttribute.invocations.get(0));
+         assertEquals(SessionSpecListenerAttribute.Type.PASSIVATED, SessionSpecListenerAttribute.invocations.get(1));
+         
+         validateNoNotifications(null, null, hsl1, hsal1, null);
+         clearNotifications(hsl0, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+         
+      }
+      
+      jbcm0.stop();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         assertEquals(1, hsl0.invocations.size());
+         assertEquals(MockHttpSessionListener.Type.DESTROYED, hsl0.invocations.get(0));
+         assertEquals(1, hsal0.invocations.size());
+         assertEquals(MockHttpSessionAttributeListener.Type.REMOVED, hsal0.invocations.get(0));
+         assertEquals(1, SessionSpecListenerAttribute.invocations.size());
+         assertEquals(SessionSpecListenerAttribute.Type.UNBOUND, SessionSpecListenerAttribute.invocations.get(0));
+         
+         validateNoNotifications(null, null, hsl1, hsal1, null);
+         clearNotifications(hsl0, hsal0, null, null, SessionSpecListenerAttribute.invocations);
+         
+         
+      }
+      
+      jbcm1.stop();
+      
+      if (!notify)
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+      else
+      {
+         validateNoNotifications(hsl0, hsal0, hsl1, hsal1);
+      }
+   }
+   
+   private void validateNoNotifications(MockHttpSessionListener hsl0, MockHttpSessionAttributeListener hsal0,
+         MockHttpSessionListener hsl1, MockHttpSessionAttributeListener hsal1)
+   {
+      validateNoNotifications(hsl0, hsal0, hsl1, hsal1, SessionSpecListenerAttribute.invocations);
+   }
+   
+   private void validateNoNotifications(MockHttpSessionListener hsl0, MockHttpSessionAttributeListener hsal0,
+         MockHttpSessionListener hsl1, MockHttpSessionAttributeListener hsal1, List<SessionSpecListenerAttribute.Type> sspalis)
+   {
+      if (hsl0 != null)
+      {
+         assertEquals(0, hsl0.invocations.size());
+      }
+      if (hsal0 != null)
+      {
+         assertEquals(0, hsal0.invocations.size());
+      }
+      if (hsl1 != null)
+      {
+         assertEquals(0, hsl1.invocations.size());
+      }
+      if (hsal1 != null)
+      {
+         assertEquals(0, hsal1.invocations.size());
+      }
+      
+      if (sspalis != null)
+      {
+         assertEquals(0, sspalis.size());         
+      }
+      
+      clearNotifications(hsl0, hsal0, hsl1, hsal1, sspalis);
+   }
+   
+   private void clearNotifications(MockHttpSessionListener hsl0, MockHttpSessionAttributeListener hsal0,
+         MockHttpSessionListener hsl1, MockHttpSessionAttributeListener hsal1, List<SessionSpecListenerAttribute.Type> sspalis)
+   {      
+
+      if (hsl0 != null)
+      {
+         hsl0.invocations.clear();
+      }
+      if (hsal0 != null)
+      {
+         hsal0.invocations.clear();
+      }
+      if (hsl1 != null)
+      {
+         hsl1.invocations.clear();
+      }
+      if (hsal1 != null)
+      {
+         hsal1.invocations.clear();
+      }
+      
+      if (sspalis != null)
+      {
+         sspalis.clear();         
+      }
+   }
+   
+   protected JBossCacheManager[] getCacheManagers(String warname, int maxInactive, int maxIdle)
+      throws Exception
+   {
+      JBossCacheManager jbcm0 = SessionTestUtil.createManager(warname, maxInactive, pojoCaches[0], null);
+      JBossWebMetaData metadata = SessionTestUtil.createWebMetaData(getReplicationGranularity(), getReplicationTrigger(), -1, maxIdle > 0, maxIdle, -1 ,false, 0);
+      // FIXME replacing setting system property w/ this:
+//      metadata.setSessionNotificationPolicy(MockClusteredSessionNotificationPolicy.class.getName());
+      jbcm0.init(warname, metadata);
+      this.managers.add(jbcm0);
+      jbcm0.start();
+      
+      JBossCacheManager jbcm1 = SessionTestUtil.createManager(warname, maxInactive, pojoCaches[1], null);
+      metadata = SessionTestUtil.createWebMetaData(getReplicationGranularity(), getReplicationTrigger(), -1, true, maxIdle, -1 ,false, 0);
+      // FIXME replacing setting system property w/ this:
+//    metadata.setSessionNotificationPolicy(MockClusteredSessionNotificationPolicy.class.getName());
+      jbcm1.init(warname, metadata);
+      this.managers.add(jbcm1);
+      jbcm1.start();
+      
+      return new JBossCacheManager[]{jbcm0, jbcm1};
+   }
+   
+   protected void validateExpectedAttributes(Map<String, Object> expected, BasicRequestHandler handler)
+   {
+      assertFalse(handler.isNewSession());
+      
+      if (handler.isCheckAttributeNames())
+      {
+         assertEquals(expected.size(), handler.getAttributeNames().size());
+      }
+      Map<String, Object> checked = handler.getCheckedAttributes();
+      assertEquals(expected.size(), checked.size());
+      for (Map.Entry<String, Object> entry : checked.entrySet())
+         assertEquals(entry.getKey(), expected.get(entry.getKey()), entry.getValue());
+      
+   }
+   
+   protected void validateNewSession(BasicRequestHandler handler)
+   {
+      assertTrue(handler.isNewSession());
+      assertEquals(handler.getCreationTime(), handler.getLastAccessedTime());
+      if (handler.isCheckAttributeNames())
+      {
+         assertEquals(0, handler.getAttributeNames().size());
+      }
+      Map<String, Object> checked = handler.getCheckedAttributes();
+      for (Map.Entry<String, Object> entry : checked.entrySet())
+         assertNull(entry.getKey(), entry.getValue());
+   }
+   
+
+}

Modified: trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java	2008-10-16 23:10:56 UTC (rev 79610)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -30,6 +30,7 @@
 import org.jboss.metadata.web.jboss.ReplicationTrigger;
 import org.jboss.web.tomcat.service.session.AbstractJBossManager;
 import org.jboss.web.tomcat.service.session.ClusteredSession;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
 
 /**
  * @author Brian Stansberry
@@ -84,7 +85,7 @@
    }
 
    @Override
-   public void initAfterLoad(AbstractJBossManager manager)
+   public void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause)
    {
       
    }

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/InvalidateSessionRequestHandler.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/InvalidateSessionRequestHandler.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/InvalidateSessionRequestHandler.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,55 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.mocks;
+
+import java.util.Set;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class InvalidateSessionRequestHandler extends BasicRequestHandler
+{   
+   /**
+    * Create a new RemoveAttributesRequestHandler.
+    * 
+    */
+   public InvalidateSessionRequestHandler(Set<String> toCheck, boolean checkNames)
+   {      
+      super(toCheck, checkNames);
+   }
+
+   public void handleRequest(Request request, Response response)
+   {
+      super.handleRequest(request, response);
+      
+      getSession().invalidate();
+   }
+   
+   
+
+   
+}

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/RemoveAttributesRequestHandler.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/RemoveAttributesRequestHandler.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/mocks/RemoveAttributesRequestHandler.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.mocks;
+
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class RemoveAttributesRequestHandler extends BasicRequestHandler
+{
+   private Set<String> toRemove;
+   
+   /**
+    * Create a new RemoveAttributesRequestHandler.
+    * 
+    */
+   public RemoveAttributesRequestHandler(Set<String> toRemove, boolean checkNames)
+   {      
+      super(toRemove, checkNames);
+      this.toRemove = toRemove;
+   }
+
+   public void handleRequest(Request request, Response response)
+   {
+      super.handleRequest(request, response);
+      
+      HttpSession session = getSession();
+      for (String key : toRemove)
+      {
+         session.removeAttribute(key);
+      }      
+   }
+   
+   
+
+   
+}

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockClusteredSessionNotificationPolicy.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockClusteredSessionNotificationPolicy.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockClusteredSessionNotificationPolicy.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,124 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.notification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicyBase;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class MockClusteredSessionNotificationPolicy extends ClusteredSessionNotificationPolicyBase
+      implements
+         ClusteredSessionNotificationPolicy
+{
+   private boolean response;
+   public final List<PolicyInvocation> invocations = new ArrayList<PolicyInvocation>();
+   
+   public enum Type { ACTIVATION, ATTRIBUTE, BINDING, SESSION };
+   
+   /* (non-Javadoc)
+    * @see org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy#isHttpSessionAttributeListenerInvocationAllowed(org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus, org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause, java.lang.String, boolean)
+    */
+   public boolean isHttpSessionAttributeListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+   {
+      invocations.add(new PolicyInvocation(Type.ATTRIBUTE, status, cause, attributeName, local));
+      return response;
+   }
+
+   /* (non-Javadoc)
+    * @see org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy#isHttpSessionBindingListenerInvocationAllowed(org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus, org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause, java.lang.String, boolean)
+    */
+   public boolean isHttpSessionBindingListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+   {
+      invocations.add(new PolicyInvocation(Type.BINDING, status, cause, attributeName, local));
+      return response;
+   }
+
+   /* (non-Javadoc)
+    * @see org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy#isHttpSessionListenerInvocationAllowed(org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus, org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause, boolean)
+    */
+   public boolean isHttpSessionListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, boolean local)
+   {
+      invocations.add(new PolicyInvocation(Type.SESSION, status, cause, null, local));
+      return response;
+   }
+   
+   
+
+   public boolean isHttpSessionActivationListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName)
+   {
+      invocations.add(new PolicyInvocation(Type.ACTIVATION, status, cause, attributeName, true));
+      return response;
+   }
+
+   public boolean getResponse()
+   {
+      return response;
+   }
+
+   public void setResponse(boolean response)
+   {
+      this.response = response;
+   }
+   
+   public List<PolicyInvocation> getInvocations()
+   {
+      return invocations;
+   }
+   
+   public void clear()
+   {
+      invocations.clear();
+   }
+   
+   public static class PolicyInvocation
+   {
+      public final Type type;
+      public final ClusteredSessionManagementStatus status;
+      public final ClusteredSessionNotificationCause cause;
+      public final String attributeName;
+      public final boolean local;
+      
+      private PolicyInvocation(Type type, ClusteredSessionManagementStatus status,
+            ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+      {
+         this.type = type;
+         this.status = status;
+         this.cause = cause;
+         this.attributeName = attributeName;
+         this.local = local;
+      }
+   }
+
+}

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionAttributeListener.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionAttributeListener.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionAttributeListener.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.notification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+
+import org.jboss.test.cluster.web.notification.SessionSpecListenerAttribute.Type;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class MockHttpSessionAttributeListener implements HttpSessionAttributeListener
+{
+   public enum Type { ADDED, REMOVED, REPLACED };
+   
+   public final List<Type> invocations = new ArrayList<Type>();
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionAttributeListener#attributeAdded(javax.servlet.http.HttpSessionBindingEvent)
+    */
+   public void attributeAdded(HttpSessionBindingEvent arg0)
+   {
+      invocations.add(Type.ADDED);
+   }
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionAttributeListener#attributeRemoved(javax.servlet.http.HttpSessionBindingEvent)
+    */
+   public void attributeRemoved(HttpSessionBindingEvent arg0)
+   {
+      invocations.add(Type.REMOVED);
+   }
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionAttributeListener#attributeReplaced(javax.servlet.http.HttpSessionBindingEvent)
+    */
+   public void attributeReplaced(HttpSessionBindingEvent arg0)
+   {
+      invocations.add(Type.REPLACED);
+   }
+
+}

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionListener.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionListener.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/MockHttpSessionListener.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.notification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class MockHttpSessionListener implements HttpSessionListener
+{
+   public enum Type { CREATED, DESTROYED };
+   
+   public final List<Type> invocations = new ArrayList<Type>();
+   
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
+    */
+   public void sessionCreated(HttpSessionEvent arg0)
+   {
+      invocations.add(Type.CREATED);
+   }
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
+    */
+   public void sessionDestroyed(HttpSessionEvent arg0)
+   {
+      invocations.add(Type.DESTROYED);
+   }
+
+}

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/SessionSpecListenerAttribute.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/SessionSpecListenerAttribute.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/SessionSpecListenerAttribute.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,80 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.test.cluster.web.notification;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class SessionSpecListenerAttribute implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable
+{
+   private static final long serialVersionUID = 1L;
+
+   public enum Type { BOUND, UNBOUND, ACTIVATING, PASSIVATED };
+   
+   public static final List<Type> invocations = new ArrayList<Type>();
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionBindingListener#valueBound(javax.servlet.http.HttpSessionBindingEvent)
+    */
+   public void valueBound(HttpSessionBindingEvent arg0)
+   {
+      getInvocations().add(Type.BOUND);
+   }
+
+   /* (non-Javadoc)
+    * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(javax.servlet.http.HttpSessionBindingEvent)
+    */
+   public void valueUnbound(HttpSessionBindingEvent arg0)
+   {
+      getInvocations().add(Type.UNBOUND);
+   }
+   
+   public void sessionDidActivate(HttpSessionEvent arg0)
+   {
+      invocations.add(Type.ACTIVATING);
+   }
+
+   public void sessionWillPassivate(HttpSessionEvent arg0)
+   {
+      invocations.add(Type.PASSIVATED);
+   }
+
+   private List<Type> getInvocations()
+   {
+//      if (invocations == null)
+//      {
+//         invocations = new ArrayList<Type>();
+//      }
+      return invocations;
+   }
+}


Property changes on: trunk/testsuite/src/main/org/jboss/test/cluster/web/notification/SessionSpecListenerAttribute.java
___________________________________________________________________
Name: svn:mergeinfo
   + 

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java	2008-10-16 23:10:56 UTC (rev 79610)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -49,6 +49,11 @@
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionTimestamp;
 
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
+import org.jboss.web.tomcat.service.session.notification.LegacyClusteredSessionNotificationPolicy;
+
 /**
  * Abstract base class for session clustering based on StandardSession. Different session
  * replication strategy can be implemented such as session- or attribute-based ones.
@@ -173,6 +178,16 @@
     * Has this session only been accessed once?
     */
    protected transient boolean firstAccess;
+   
+   /**
+    * Policy that drives whether we issue servlet spec notifications.
+    */
+   protected transient ClusteredSessionNotificationPolicy notificationPolicy;
+   
+   protected transient ClusteredSessionManagementStatus clusterStatus;
+   
+   /** True if a call to activate() is needed to offset a preceding passivate() call */
+   protected transient boolean needsPostReplicateActivation;
 
    /**
     * The string manager for this package.
@@ -186,6 +201,7 @@
       invalidationPolicy = replicationTrigger;
       this.useJK = useJK;
       this.firstAccess = true;
+      this.notificationPolicy = new LegacyClusteredSessionNotificationPolicy();
       checkAlwaysReplicateTimestamp();
    }
 
@@ -398,8 +414,9 @@
     * This is called after loading a session to initialize the transient values.
     *
     * @param manager
+    * @param cause the cause of the load
     */
-   public abstract void initAfterLoad(AbstractJBossManager manager);
+   public abstract void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause);
 
    /**
     * Propogate session to the internal store.
@@ -493,11 +510,15 @@
          throw new IllegalArgumentException
             (sm.getString("clusteredSession.setAttribute.iae"));
       }
+      
+      ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
+      
       // Construct an event with the new value
       HttpSessionBindingEvent event = null;
 
       // Call the valueBound() method if necessary
-      if (value instanceof HttpSessionBindingListener)
+      if (value instanceof HttpSessionBindingListener
+            && policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
       {
          event = new HttpSessionBindingEvent(getSession(), name, value);
          try
@@ -515,7 +536,8 @@
 
       // Call the valueUnbound() method if necessary
       if ((unbound != null) && (unbound != value) &&
-         (unbound instanceof HttpSessionBindingListener))
+         (unbound instanceof HttpSessionBindingListener) &&
+         policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
       {
          try
          {
@@ -529,71 +551,74 @@
       }
 
       // Notify interested application event listeners
-      Context context = (Context) manager.getContainer();
-      Object listeners[] = context.getApplicationEventListeners();
-      if (listeners == null)
-         return;
-      for (int i = 0; i < listeners.length; i++)
-      {
-         if (!(listeners[i] instanceof HttpSessionAttributeListener))
-            continue;
-         HttpSessionAttributeListener listener =
-            (HttpSessionAttributeListener) listeners[i];
-         try
+      if (policy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
+      {         
+         Context context = (Context) manager.getContainer();
+         Object lifecycleListeners[] = context.getApplicationEventListeners();
+         if (lifecycleListeners == null)
+            return;
+         for (int i = 0; i < lifecycleListeners.length; i++)
          {
-            if (unbound != null)
-            {
-               fireContainerEvent(context,
-                  "beforeSessionAttributeReplaced",
-                  listener);
-               if (event == null)
-               {
-                  event = new HttpSessionBindingEvent
-                     (getSession(), name, unbound);
-               }
-               listener.attributeReplaced(event);
-               fireContainerEvent(context,
-                  "afterSessionAttributeReplaced",
-                  listener);
-            }
-            else
-            {
-               fireContainerEvent(context,
-                  "beforeSessionAttributeAdded",
-                  listener);
-               if (event == null)
-               {
-                  event = new HttpSessionBindingEvent
-                     (getSession(), name, value);
-               }
-               listener.attributeAdded(event);
-               fireContainerEvent(context,
-                  "afterSessionAttributeAdded",
-                  listener);
-            }
-         }
-         catch (Throwable t)
-         {
+            if (!(lifecycleListeners[i] instanceof HttpSessionAttributeListener))
+               continue;
+            HttpSessionAttributeListener listener =
+               (HttpSessionAttributeListener) lifecycleListeners[i];
             try
             {
                if (unbound != null)
                {
                   fireContainerEvent(context,
+                     "beforeSessionAttributeReplaced",
+                     listener);
+                  if (event == null)
+                  {
+                     event = new HttpSessionBindingEvent
+                        (getSession(), name, unbound);
+                  }
+                  listener.attributeReplaced(event);
+                  fireContainerEvent(context,
                      "afterSessionAttributeReplaced",
                      listener);
                }
                else
                {
                   fireContainerEvent(context,
+                     "beforeSessionAttributeAdded",
+                     listener);
+                  if (event == null)
+                  {
+                     event = new HttpSessionBindingEvent
+                        (getSession(), name, value);
+                  }
+                  listener.attributeAdded(event);
+                  fireContainerEvent(context,
                      "afterSessionAttributeAdded",
                      listener);
                }
             }
-            catch (Exception e)
+            catch (Throwable t)
             {
-               ;
+               try
+               {
+                  if (unbound != null)
+                  {
+                     fireContainerEvent(context,
+                        "afterSessionAttributeReplaced",
+                        listener);
+                  }
+                  else
+                  {
+                     fireContainerEvent(context,
+                        "afterSessionAttributeAdded",
+                        listener);
+                  }
+               }
+               catch (Exception e)
+               {
+                  ;
+               }
+               manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
             }
-            manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
          }
       }
    }
@@ -630,7 +655,7 @@
       boolean notify = true;
       boolean localCall = true;
       boolean localOnly = false;
-      expire(notify, localCall, localOnly);
+      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE);
    }
     
     
@@ -695,7 +720,7 @@
    {
       boolean localCall = true;
       boolean localOnly = true;
-      expire(notify, localCall, localOnly);
+      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.TIMEOUT);
    }
 
    /**
@@ -720,8 +745,9 @@
     *                   cluster nodes should be made aware of the expiration.
     *                   Only meaningful if <code>localCall</code> is
     *                   <code>true</code>.
+    * @param cause the cause of the expiration
     */
-   public void expire(boolean notify, boolean localCall, boolean localOnly)
+   public void expire(boolean notify, boolean localCall, boolean localOnly, ClusteredSessionNotificationCause cause)
    {
       if (log.isTraceEnabled())
       {
@@ -748,18 +774,20 @@
          // Notify interested application event listeners
          // FIXME - Assumes we call listeners in reverse order
          Context context = (Context) manager.getContainer();
-         Object listeners[] = context.getApplicationLifecycleListeners();
-         if (notify && (listeners != null))
+         Object lifecycleListeners[] = context.getApplicationLifecycleListeners();
+         if (notify 
+               && (lifecycleListeners != null) 
+               && getNotificationPolicy().isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, localCall))
          {
             HttpSessionEvent event =
                new HttpSessionEvent(getSession());
-            for (int i = 0; i < listeners.length; i++)
+            for (int i = 0; i < lifecycleListeners.length; i++)
             {
-               int j = (listeners.length - 1) - i;
-               if (!(listeners[j] instanceof HttpSessionListener))
+               int j = (lifecycleListeners.length - 1) - i;
+               if (!(lifecycleListeners[j] instanceof HttpSessionListener))
                   continue;
                HttpSessionListener listener =
-                  (HttpSessionListener) listeners[j];
+                  (HttpSessionListener) lifecycleListeners[j];
                try
                {
                   fireContainerEvent(context,
@@ -786,6 +814,7 @@
                }
             }
          }
+         
          if (ACTIVITY_CHECK) {
              accessCount.set(0);
          }
@@ -799,7 +828,7 @@
          // JBAS-1360 -- Unbind any objects associated with this session
          String keys[] = keys();
          for (int i = 0; i < keys.length; i++)
-             removeAttributeInternal(keys[i], localCall, localOnly, notify);
+             removeAttributeInternal(keys[i], localCall, localOnly, notify, cause);
 
          // Remove this session from our manager's active sessions
          removeFromManager(localCall, localOnly);
@@ -832,7 +861,13 @@
       }
    }
 
+   @Override
    public void passivate()
+   {      
+      passivate(ClusteredSessionNotificationCause.PASSIVATION);
+   }
+
+   public void passivate(ClusteredSessionNotificationCause cause)
    {
       // Notify interested session event listeners
       fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
@@ -841,6 +876,8 @@
       {
          boolean hasListener = false;
          
+         ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
+         
          // Notify ActivationListeners
          HttpSessionEvent event = null;
          String keys[] = keys();
@@ -852,26 +889,40 @@
             {
                hasListener = true;
                
-               if (event == null)
-                  event = new HttpSessionEvent(getSession());
-               try 
-               {
-                  ((HttpSessionActivationListener)attribute).sessionWillPassivate(event);
+               if (policy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
+               {                  
+                  if (event == null)
+                     event = new HttpSessionEvent(getSession());
+                  
+                  try 
+                  {
+                     ((HttpSessionActivationListener)attribute).sessionWillPassivate(event);
+                  }
+                  catch (Throwable t) 
+                  {
+                     manager.getContainer().getLogger().error
+                            (sm.getString("clusteredSession.attributeEvent"), t);
+                  }
                }
-               catch (Throwable t) 
-               {
-                  manager.getContainer().getLogger().error
-                         (sm.getString("clusteredSession.attributeEvent"), t);
-               }
             }
          }
          
          hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
       }
+      
+      if (cause != ClusteredSessionNotificationCause.PASSIVATION)
+      {
+         this.needsPostReplicateActivation = true;
+      }
    }
 
    public void activate()
    {
+      activate(ClusteredSessionNotificationCause.ACTIVATION);
+   }
+
+   public void activate(ClusteredSessionNotificationCause cause)
+   {
       // Notify interested session event listeners
       fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
 
@@ -881,6 +932,8 @@
 
          boolean hasListener = false;
          
+         ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
+         
          HttpSessionEvent event = null;
          String keys[] = keys();
          Map attrs = getAttributesInternal();
@@ -890,23 +943,37 @@
             if (attribute instanceof HttpSessionActivationListener) 
             {
                hasListener = true;
-               if (event == null)
-                  event = new HttpSessionEvent(getSession());
-               try 
+               
+               if (policy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
                {
-                  ((HttpSessionActivationListener)attribute).sessionDidActivate(event);
+                  if (event == null)
+                     event = new HttpSessionEvent(getSession());
+                  try 
+                  {
+                     ((HttpSessionActivationListener)attribute).sessionDidActivate(event);
+                  }
+                  catch (Throwable t) 
+                  {
+                     manager.getContainer().getLogger().error
+                            (sm.getString("clusteredSession.attributeEvent"), t);
+                  }
                }
-               catch (Throwable t) 
-               {
-                  manager.getContainer().getLogger().error
-                         (sm.getString("clusteredSession.attributeEvent"), t);
-               }
             }
          }
          
          hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
       }
+      
+      if (cause != ClusteredSessionNotificationCause.ACTIVATION)
+      {
+         this.needsPostReplicateActivation = false;
+      }
    }
+   
+   public boolean getNeedsPostReplicateActivation()
+   {
+      return needsPostReplicateActivation;
+   }
 
    // TODO uncomment when work on JBAS-1900 is completed
 //   public void removeNote(String name)
@@ -972,6 +1039,8 @@
       lastReplicated = 0;
       maxUnreplicatedInterval = 0;
       alwaysReplicateTimestamp = true;
+      this.notificationPolicy = null;
+      this.clusterStatus = null;
    }
    
    /**
@@ -995,8 +1064,23 @@
       // Parse the real id first, as super.setId() calls add(),
       // which depends on having the real id
       parseRealId(id);
-      super.setId(id);
+      
+      // TODO -- should we bypass this if realId hasn't changed? We're removing
+      // and readding every time we fail over, when all we want is a 
+      // jvmRoute change to the session id
+      
+      if ((this.id != null) && (manager != null))
+          manager.remove(this);
+
+      this.id = id;
+      
+      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
+
+      if (manager != null)
+          manager.add(this);
    }
+   
+   
 
    /**
     * Set the authenticated Principal that is associated with this Session.
@@ -1037,6 +1121,53 @@
       sessionMetadataDirty();
    }
 
+   @Override
+   public void tellNew() 
+   {
+      tellNew(ClusteredSessionNotificationCause.CREATE);
+   }
+
+   public void tellNew(ClusteredSessionNotificationCause cause)
+   {
+      // Notify interested session event listeners
+      fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
+
+      // Notify interested application event listeners
+      if (getNotificationPolicy().isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, true))
+      {
+         Context context = (Context) manager.getContainer();
+         Object lifecycleListeners[] = context.getApplicationLifecycleListeners();
+         if (lifecycleListeners != null)
+         {
+            HttpSessionEvent event = new HttpSessionEvent(getSession());
+            for (int i = 0; i < lifecycleListeners.length; i++)
+            {
+               if (!(lifecycleListeners[i] instanceof HttpSessionListener))
+                  continue;
+               HttpSessionListener listener = (HttpSessionListener) lifecycleListeners[i];
+               try
+               {
+                  fireContainerEvent(context, "beforeSessionCreated", listener);
+                  listener.sessionCreated(event);
+                  fireContainerEvent(context, "afterSessionCreated", listener);
+               }
+               catch (Throwable t)
+               {
+                  try
+                  {
+                     fireContainerEvent(context, "afterSessionCreated", listener);
+                  }
+                  catch (Exception e)
+                  {
+                     ;
+                  }
+                  manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
+               }
+            }
+         }
+      }
+   }
+
    public String toString()
    {
       StringBuffer buf = new StringBuffer();
@@ -1081,6 +1212,16 @@
       
       return excluded;      
    }
+
+   protected ClusteredSessionNotificationPolicy getNotificationPolicy()
+   {
+      return notificationPolicy;
+   }
+
+   protected void setNotificationPolicy(ClusteredSessionNotificationPolicy notificationPolicy)
+   {
+      this.notificationPolicy = notificationPolicy;
+   } 
    
    public void update(Integer version, DistributableSessionTimestamp timestamp, 
                       DistributableSessionMetadata metadata, Map attributes)
@@ -1120,6 +1261,10 @@
       // a heavy cost.
       this.lastReplicated = this.creationTime;
       
+      this.notificationPolicy = new LegacyClusteredSessionNotificationPolicy();
+      
+      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
+      
       checkAlwaysReplicateTimestamp();
       
       // TODO uncomment when work on JBAS-1900 is completed      
@@ -1170,7 +1315,7 @@
    {
       boolean localCall = true;
       boolean localOnly = false;
-      removeAttributeInternal(name, localCall, localOnly, notify);
+      removeAttributeInternal(name, localCall, localOnly, notify, ClusteredSessionNotificationCause.MODIFY);
    }
    
    /**
@@ -1186,11 +1331,13 @@
     * @param localOnly <code>true</code> if the removal should not be
     *                  replicated around the cluster
     * @param notify    <code>true</code> if listeners should be notified
+    * @param cause the cause of the removal
     */
    protected void removeAttributeInternal(String name, 
                                           boolean localCall, 
                                           boolean localOnly,
-                                          boolean notify)
+                                          boolean notify, 
+                                          ClusteredSessionNotificationCause cause)
    {
       // Remove this attribute from our collection
       Object value = removeJBossInternalAttribute(name, localCall, localOnly);
@@ -1201,53 +1348,59 @@
          return;
       }
 
+      ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
+      
       // Call the valueUnbound() method if necessary
       HttpSessionBindingEvent event = null;
-      if (value instanceof HttpSessionBindingListener)
+      if (value instanceof HttpSessionBindingListener
+            && policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
       {
          event = new HttpSessionBindingEvent(getSession(), name, value);
          ((HttpSessionBindingListener) value).valueUnbound(event);
       }
 
       // Notify interested application event listeners
-      Context context = (Context) manager.getContainer();
-      Object listeners[] = context.getApplicationEventListeners();
-      if (listeners == null)
-         return;
-      for (int i = 0; i < listeners.length; i++)
+      if (policy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
       {
-         if (!(listeners[i] instanceof HttpSessionAttributeListener))
-            continue;
-         HttpSessionAttributeListener listener =
-            (HttpSessionAttributeListener) listeners[i];
-         try
+         Context context = (Context) manager.getContainer();
+         Object lifecycleListeners[] = context.getApplicationEventListeners();
+         if (lifecycleListeners == null)
+            return;
+         for (int i = 0; i < lifecycleListeners.length; i++)
          {
-            fireContainerEvent(context,
-               "beforeSessionAttributeRemoved",
-               listener);
-            if (event == null)
-            {
-               event = new HttpSessionBindingEvent
-                  (getSession(), name, value);
-            }
-            listener.attributeRemoved(event);
-            fireContainerEvent(context,
-               "afterSessionAttributeRemoved",
-               listener);
-         }
-         catch (Throwable t)
-         {
+            if (!(lifecycleListeners[i] instanceof HttpSessionAttributeListener))
+               continue;
+            HttpSessionAttributeListener listener =
+               (HttpSessionAttributeListener) lifecycleListeners[i];
             try
             {
                fireContainerEvent(context,
+                  "beforeSessionAttributeRemoved",
+                  listener);
+               if (event == null)
+               {
+                  event = new HttpSessionBindingEvent
+                     (getSession(), name, value);
+               }
+               listener.attributeRemoved(event);
+               fireContainerEvent(context,
                   "afterSessionAttributeRemoved",
                   listener);
             }
-            catch (Exception e)
+            catch (Throwable t)
             {
-               ;
+               try
+               {
+                  fireContainerEvent(context,
+                     "afterSessionAttributeRemoved",
+                     listener);
+               }
+               catch (Exception e)
+               {
+                  ;
+               }
+               manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
             }
-            manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
          }
       }
 

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java	2008-10-16 23:10:56 UTC (rev 79610)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -24,6 +24,7 @@
 
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
 
 /**
  * Common superclass of ClusteredSession types that use JBossCache
@@ -52,6 +53,7 @@
       super(manager, manager.getReplicationTrigger(), manager.getUseJK());
       int maxUnrep = manager.getMaxUnreplicatedInterval() * 1000;
       setMaxUnreplicatedInterval(maxUnrep);
+      establishNotificationPolicy();
       establishProxy();
    }
 
@@ -61,19 +63,26 @@
     *
     * @param manager the manager for this session
     */
-   public void initAfterLoad(AbstractJBossManager manager)
+   @Override
+   public void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause)
    {
-      // Our manager and proxy may have been lost if we were recycled,
-      // so reestablish them
+      // Our manager, notification policy and proxy may have been lost 
+      // if we were replicated, so reestablish them
       setManager(manager);
+      establishNotificationPolicy();
       establishProxy();
 
       // Since attribute map may be transient, we may need to populate it 
       // from the underlying store.
       populateAttributes();
       
+      if (cause == ClusteredSessionNotificationCause.ACTIVATION)
+      {
+         this.needsPostReplicateActivation = true;
+      }
+      
       // Notify all attributes of type HttpSessionActivationListener (SRV 7.7.2)
-      this.activate();
+      this.activate(cause);
       
       // We are no longer outdated vis a vis distributed cache
       clearOutdated();
@@ -178,5 +187,10 @@
    {
       return removeJBossInternalAttribute(name, localCall, localOnly);
    }
+   
+   protected void establishNotificationPolicy()
+   {
+      setNotificationPolicy(((JBossCacheManager)manager).getNotificationPolicy());
+   }
 
 }

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2008-10-16 23:10:56 UTC (rev 79610)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -56,6 +56,11 @@
 import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
 
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCapability;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
+import org.jboss.web.tomcat.service.session.notification.LegacyClusteredSessionNotificationPolicy;
+
 /**
  * Implementation of a clustered session manager for
  * catalina using JBossCache replication.
@@ -133,6 +138,9 @@
    
    private int maxUnreplicatedInterval_ = -1;
 
+   private String notificationPolicyClass_;
+   private ClusteredSessionNotificationPolicy notificationPolicy_;
+
    //  ----------------------------------------------------------  Constructors
 
    public JBossCacheManager() throws ClusteringNotSupportedException
@@ -199,6 +207,9 @@
       
       this.cacheConfigName_ = repCfg.getCacheName();
       
+      // FIXME -- JBAS-5778
+      //this.notificationPolicyClass_ = webMetaData.getSessionNotificationPolicy();
+      
       // Initing the proxy would be better in start, but we do it here so we
       // can detect ClusteringNotSupportedException at this deploy stage
       initCacheProxy();
@@ -277,7 +288,7 @@
 
                // Notify all session attributes that they get serialized (SRV 7.7.2)
                long begin = System.currentTimeMillis();
-               session.passivate();
+               session.passivate(ClusteredSessionNotificationCause.REPLICATION);
                long elapsed = System.currentTimeMillis() - begin;
                stats_.updatePassivationStats(realId, elapsed);
 
@@ -442,6 +453,7 @@
       }
 
       session.setId(sessionId); // Setting the id leads to a call to add()
+      session.tellNew(ClusteredSessionNotificationCause.CREATE);
 
       if (trace_)
       {
@@ -492,12 +504,15 @@
             log_.trace("Checking for session " + realId + " in the distributed cache");
          
          session = loadSession(realId);
-         if (session != null)
-         {
-            add(session);
-            // TODO should we advise of a new session?
-            //tellNew();
-         }
+//       if (session != null)
+//       {
+//          add(session);
+//          // We now notify, since we've added a policy to allow listeners 
+//          // to discriminate. But the default policy will not allow the 
+//          // notification to be emitted for FAILOVER, so the standard
+//          // behavior is unchanged.
+//          session.tellNew(ClusteredSessionNotificationCause.FAILOVER);
+//       }
       }
       else if (session != null && session.isOutdated())
       {
@@ -512,6 +527,13 @@
       {
          // Add this session to the set of those potentially needing replication
          SessionReplicationContext.bindSession(session, snapshotManager_);
+         
+         // If we previously called passivate() on the session due to
+         // replication, we need to make an offsetting activate() call
+         if (session.getNeedsPostReplicateActivation())
+         {
+            session.activate(ClusteredSessionNotificationCause.REPLICATION);
+         }
       }
 
       return session;
@@ -920,7 +942,7 @@
          try
          {
             Thread.currentThread().setContextClassLoader(tcl_);
-            session.expire(notify, localCall, localOnly);
+            session.expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE);
          }
          finally
          {
@@ -1153,6 +1175,16 @@
       return proxy_;
    }
 
+   public ClusteredSessionNotificationPolicy getNotificationPolicy()
+   {
+      return notificationPolicy_;
+   }
+
+   public void setNotificationPolicy_(ClusteredSessionNotificationPolicy notificationPolicy_)
+   {
+      this.notificationPolicy_ = notificationPolicy_;
+   }
+
    // --------------------------------------------------------------- Overrides
 
    /**
@@ -1429,6 +1461,8 @@
 
       long begin = System.currentTimeMillis();
       boolean mustAdd = false;
+      boolean passivated = false;
+      
       JBossCacheClusteredSession session = (JBossCacheClusteredSession) sessions_.get(realId);
       
       if (session == null)
@@ -1438,6 +1472,9 @@
          // a replication message from another server
          mustAdd = true;
          session = createEmptyClusteredSession();
+         
+         OwnedSessionUpdate osu = unloadedSessions_.get(realId);
+         passivated = (osu != null && osu.passivated);
       }
 
       synchronized (session)
@@ -1469,7 +1506,9 @@
             
             if (session != null)
             {
-               session.initAfterLoad(this);
+               ClusteredSessionNotificationCause cause = passivated ? ClusteredSessionNotificationCause.ACTIVATION 
+                                                                    : ClusteredSessionNotificationCause.FAILOVER;
+               session.initAfterLoad(this, cause);
             }
          }
          catch (Exception ex)
@@ -1511,7 +1550,13 @@
          if (session != null)
          {            
             if (mustAdd)
+            {
                add(session, false); // don't replicate
+               if (!passivated)
+               {
+                  session.tellNew(ClusteredSessionNotificationCause.FAILOVER);
+               }
+            }
             long elapsed = System.currentTimeMillis() - begin;
             stats_.updateLoadStats(realId, elapsed);
 
@@ -1624,7 +1669,7 @@
                // Tell the proxy to ignore cache notifications we are about
                // to generate for this session.
                SessionReplicationContext.startCacheActivity();
-               session.passivate();
+               session.passivate(ClusteredSessionNotificationCause.PASSIVATION);
                proxy_.evictSession(realId);
                sessionPassivated();
             }
@@ -1727,9 +1772,9 @@
             log_.error(msg, e);
             throw new LifecycleException(msg, e);
          }
-      }
+      }      
       
-      // Validate attributes
+      initClusteredSessionNotificationPolicy();
       
       // Create the JBossCacheService
       try
@@ -1796,6 +1841,32 @@
          proxy_ = distributedCacheManagerFactory.getDistributedCacheManager(cacheConfigName_);
       }
    }
+
+   private void initClusteredSessionNotificationPolicy()
+   {
+      if (this.notificationPolicyClass_ == null || this.notificationPolicyClass_.length() == 0)
+      {
+         this.notificationPolicyClass_ = System.getProperty("jboss.web.clustered.session.notification.policy",
+                                                            LegacyClusteredSessionNotificationPolicy.class.getName());
+      }
+      
+      try
+      {
+         this.notificationPolicy_ = (ClusteredSessionNotificationPolicy) Thread.currentThread().getContextClassLoader().loadClass(this.notificationPolicyClass_).newInstance();
+      }
+      catch (RuntimeException e)
+      {
+         throw e;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("Failed to instantiate " + 
+               ClusteredSessionNotificationPolicy.class.getName() + 
+               " " + this.notificationPolicyClass_, e);
+      }
+      
+      this.notificationPolicy_.setClusteredSessionNotificationCapability(new ClusteredSessionNotificationCapability());      
+   }
    
    /**
     * Gets the ids of all sessions in the distributed cache and adds
@@ -2039,7 +2110,7 @@
                boolean notify = true;
                boolean localCall = true;
                boolean localOnly = true;
-               ses.expire(notify, localCall, localOnly);               
+               ses.expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.UNDEPLOY);               
             }
          }
          catch (Throwable t)
@@ -2098,6 +2169,8 @@
    {
       super.start();
       
+      initClusteredSessionNotificationPolicy();
+      
       // Start the JBossCacheService
       // Will need to pass the classloader that is associated with this 
       // web app so de-serialization will work correctly.

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionManagementStatus.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionManagementStatus.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionManagementStatus.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,127 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+/**
+ * Encapsulates the status of how the local container is managing the
+ * given session.
+ * 
+ * @author Brian Stansberry
+ */
+public class ClusteredSessionManagementStatus 
+{
+   private final String  realId;
+   private final boolean locallyUsed;
+   private final Boolean locallyActive;
+   private final Boolean locallyOwned;
+   
+   /**
+    * Create a new ClusteredSessionManagementStatus.
+    * 
+    * @param realId the id of the session, excluding any jvmRoute.
+    * @param locallyUsed whether the session has been provided to the 
+    *                    application on this node.
+    * @param locallyActive whether this node is the most recent one to 
+    *                      handle a request for the session; <code>null</code>
+    *                      if unknown
+    * @param locallyOwned  whether this node is the "owner" of the session,
+    *                      <code>null</code> if unknown or the concept of
+    *                      ownership is unsupported.
+    */
+   public ClusteredSessionManagementStatus(String realId,
+                                           boolean locallyUsed, 
+                                           Boolean locallyActive, 
+                                           Boolean locallyOwned)
+   {
+      if (realId == null)
+         throw new IllegalArgumentException("realId is null");
+      
+      this.realId = realId;
+      this.locallyUsed = locallyUsed;
+      // If we haven't been locallyUsed, we can't be locallyActive
+      this.locallyActive = (locallyUsed ? locallyActive : Boolean.FALSE);
+      // If we are locallyActive, we are locally owned
+      this.locallyOwned = (Boolean.TRUE.equals(locallyActive) ? Boolean.TRUE : locallyOwned);
+   }
+   
+   /**
+    * Gets the id of the session, excluding any jvmRoute that may have
+    * been appended if JK is used.
+    * 
+    * @return the id. Will not return <code>null</code>. 
+    */
+   public String getRealId()
+   {
+      return realId;
+   }
+
+   /**
+    * Gets whether an HttpSession object for the given session has been
+    * returned from the container to the application on this node.
+    * 
+    * @return <code>true</code> if the session has been used locally, 
+    *         <code>false</code> if not.
+    */
+   public boolean isLocallyUsed()
+   {
+      return locallyUsed;
+   }
+
+   /**
+    * Gets whether an HttpSession object for the given session has been
+    * returned from the container to the application on this node AND
+    * this node is the last one to handle a request for the session.
+    * 
+    * @return <code>true</code> if the above conditions are true and the 
+    *         container is sure of this, <code>false</code> if they are not
+    *         true and the container knows this, or <code>null</code> if the
+    *         container is unsure if this node is the last one to handle a 
+    *         request for the session.
+    *         
+    * @see ClusteredSessionNotificationCapability#isLocallyActiveAware()
+    */
+   public Boolean getLocallyActive()
+   {
+      return locallyActive;
+   }
+
+   /**
+    * Gets whether this node considers itself to be the "owner" of the session;
+    * i.e. the one primarily responsible for managing its lifecycle. Note that
+    * a node that is undeploying a war will always give up ownership of its
+    * sessions if it is aware of other nodes in the cluster that still have
+    * the war deployed.
+    * 
+    * @return <code>true</code> if the container knows it is the owner,
+    *         <code>false</code> if it knows it is not the owner, or 
+    *         <code>null</code> if the container is unsure about ownership
+    *         or does not recognize the concept of ownership.
+    *         
+    * @see ClusteredSessionNotificationCapability#isLocallyOwnedAware()
+    */
+   public Boolean getLocallyOwned()
+   {
+      return locallyOwned;
+   }   
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCapability.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCapability.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCapability.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,133 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+
+/**
+ * Encapsulates information about the container's capability to issue
+ * servlet spec notifications under different condititions. Implementations of
+ * {@link ClusteredSessionNotificationPolicy} can use this 
+ * information to get a sense of the capabilities of the container.
+ * 
+ * @author Brian Stansberry
+ */
+public class ClusteredSessionNotificationCapability
+{
+
+   /**
+    * Does the container support invoking <code>HttpSessionListener</code>
+    * callbacks under the given conditions?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is supported, 
+    *         <code>false</code> if not
+    */
+   public boolean isHttpSessionListenerInvocationSupported(ClusteredSessionManagementStatus status, 
+                                                           ClusteredSessionNotificationCause cause, 
+                                                           boolean local)
+   {
+      return local && status.isLocallyUsed() && !ClusteredSessionNotificationCause.STATE_TRANSFER.equals(cause);
+   }
+
+   /**
+    * Under the given conditions, does the container support invoking 
+    * <code>HttpSessionAttributeListener</code> callbacks?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is supported, 
+    *         <code>false</code> if not
+    */
+   public boolean isHttpSessionAttributeListenerInvocationSupported(ClusteredSessionManagementStatus status, 
+                                                           ClusteredSessionNotificationCause cause, 
+                                                           boolean local)
+   {
+      return local && status.isLocallyUsed() && !ClusteredSessionNotificationCause.STATE_TRANSFER.equals(cause);
+   }
+
+   /**
+    * Under the given conditions, does the container support invoking 
+    * <code>HttpSessionBindingListener</code> callbacks?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is supported, 
+    *         <code>false</code> if not
+    */
+   public boolean isHttpSessionBindingListenerInvocationSupported(ClusteredSessionManagementStatus status, 
+                                                           ClusteredSessionNotificationCause cause, 
+                                                           boolean local)
+   {
+      return local && status.isLocallyUsed() && !ClusteredSessionNotificationCause.STATE_TRANSFER.equals(cause);
+   }
+   
+   /**
+    * Is the container able to distinguish whether a session that has been
+    * {@link ClusteredSessionManagementStatus#isLocallyUsed() locally used}
+    * is also {@link ClusteredSessionManagementStatus#getLocallyActive() locally active}?
+    * 
+    * @return <code>true</code> if the container is able to make this distinction;
+    *         <code>false</code> if not
+    */
+   public boolean isLocallyActiveAware()
+   {
+      return false;
+   }
+   
+   /**
+    * Is the container able to distinguish whether a session is
+    * {@link ClusteredSessionManagementStatus#getLocallyOwned() locally owned}?
+    * 
+    * @return <code>true</code> if the container is able to make this distinction;
+    *         <code>false</code> if not
+    */
+   public boolean isLocallyOwnedAware()
+   {
+      return false;
+   }
+   
+   /**
+    * Returns whether the local container is aware of events on remote nodes 
+    * that could give rise to notifications.
+    * 
+    * @param cause the cause
+    * @return <code>true</code> if the local container is aware of the
+    *         remote event, <code>false</code> if not.
+    */
+   public boolean isRemoteCauseAware(ClusteredSessionNotificationCause cause)
+   {
+      return ClusteredSessionNotificationCause.CREATE.equals(cause) 
+                || ClusteredSessionNotificationCause.MODIFY.equals(cause) 
+                || ClusteredSessionNotificationCause.INVALIDATE.equals(cause);
+   }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCause.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCause.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationCause.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,115 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+/**
+ * Reasons why a servlet spec notification for a clustered session is being 
+ * generated.
+ * 
+ * @author Brian Stansberry
+ */
+public enum ClusteredSessionNotificationCause 
+{ 
+   /**
+    * Session has been newly created.
+    */
+   CREATE, 
+   
+   /**
+    * Session has been modified by the application.
+    */
+   MODIFY, 
+   
+   /**
+    * Session has failed over and is now in use on the local node.
+    */
+   FAILOVER,
+   
+   /**
+    * Session has failed over and is no longer active on the local node.
+    */
+   FAILAWAY,
+   
+   /**
+    * Session is being invalidated by the application.
+    */
+   INVALIDATE, 
+   
+   /**
+    * Session is being expired by the container due to timeout.
+    */
+   TIMEOUT, 
+   
+   /**
+    * Session is being expired by the container due to undeploy of the
+    * web application.
+    */
+   UNDEPLOY,
+   
+   /** 
+    * Local node became aware of a session active on another node as
+    * a result of the local node receiving a bulk state transfer due to
+    * its being elected to provide backup for that other node's sessions. 
+    */
+   STATE_TRANSFER,
+   
+   /**
+    * The session is being passivated.
+    */
+   PASSIVATION,
+   
+   /**
+    * The session is being activated.
+    */
+   ACTIVATION,
+   
+   /**
+    * The session is being replicated.
+    */
+   REPLICATION//, 
+   
+//   /**
+//    * Local node has taken "ownership" of a session for a reason other than
+//    * failover; i.e. the session hasn't become active on the local node. In this
+//    * case the local node would have become aware of the session earlier (i.e.
+//    * via {@link #CREATE} or {@link #STATE_TRANSFER}) and these notifications
+//    * would signal the local node taking greater responsibility for the session.
+//    * Typically a policy implementation would not allow notifications for a
+//    * remotely originated CREATE or for a STATE_TRANSFER if it allows 
+//    * notifications for TAKE_OWNERSHIP, and vice versa. Otherwise, multiple
+//    * notifications would be received for the same session.
+//    */
+//   TAKE_OWNERSHIP,
+//   
+//   /**
+//    * Local node has relinquised "ownership" of a session for a reason other than
+//    * {@link #FAILAWAY} {@link #INVALIDATE}, {@link #TIMEOUT} or {@link #UNDEPLOY}; 
+//    * i.e. some other node is taking over as the owner of session.
+//    * Typically a policy implementation would not allow notifications for a
+//    * remotely originated CREATE or for a STATE_TRANSFER if it allows 
+//    * notifications for TAKE_OWNERSHIP, and vice versa. Otherwise, multiple
+//    * notifications would be received for the same session.
+//    */
+//   RELINQUISH_OWNERSHIP
+   
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicy.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicy.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicy.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,132 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+/**
+ * Policy for determining whether the servlet spec notifications related
+ * to session events are allowed to be emitted on the local cluster node.
+ * <p>
+ * <strong>Note:</strong> The use of the word <strong>allowed</strong> above
+ * is intentional; if a given policy implementation returns <code>true</code>
+ * from one of the methods in this interface, that does not mean the listener
+ * will be invoked by the container, nor does the presence of a method in this 
+ * interface imply that it will be invoked by the container in all cases. The
+ * only contract this interface creates is that before invoking a listener
+ * method, the container will invoke an implementation of this policy to 
+ * get permission and will not invoke the listeners if this policy returns
+ * <code>false</code>. If the container does not support emitting notifications
+ * in certain cases, it may not bother checking if the notification is allowed,
+ * and even if it checks, it still will not emit the notification.
+ * </p>
+ * <p>
+ * An example of a case where the container may not support emitting a 
+ * notification is for a session that has never been used locally.
+ * </p>
+ * 
+ * @author Brian Stansberry
+ */
+public interface ClusteredSessionNotificationPolicy
+{
+   /**
+    * Are invocations of <code>HttpSessionListener</code> callbacks
+    * allowed under the given conditions?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is allowed, 
+    *         <code>false</code> if not
+    */
+   boolean isHttpSessionListenerInvocationAllowed(ClusteredSessionManagementStatus status, 
+                                                  ClusteredSessionNotificationCause cause, 
+                                                  boolean local);
+
+
+   /**
+    * Under the given conditions, are invocations of
+    * <code>HttpSessionAttributeListener</code> callbacks allowed?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param attributeName value that would be passed to the <code>name</code>
+    *                      param of the <code>HttpSessionBindingEvent</code> if
+    *                      the listener were invoked
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is allowed, 
+    *         <code>false</code> if not
+    */
+   boolean isHttpSessionAttributeListenerInvocationAllowed(ClusteredSessionManagementStatus status, 
+                                                           ClusteredSessionNotificationCause cause, 
+                                                           String attributeName,
+                                                           boolean local);
+   
+   /**
+    * Under the given conditions, are invocations of
+    * <code>HttpSessionBindingListener</code> callbacks allowed?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param attributeName value that would be passed to the <code>name</code>
+    *                      param of the <code>HttpSessionBindingEvent</code> if
+    *                      the listener were invoked
+    * @param local  <code>true</code> if the event driving the notification 
+    *               originated on this node; <code>false</code> otherwise
+    *               
+    * @return <code>true</code> if the notification is allowed, 
+    *         <code>false</code> if not
+    */
+   boolean isHttpSessionBindingListenerInvocationAllowed(ClusteredSessionManagementStatus status, 
+                                                         ClusteredSessionNotificationCause cause,  
+                                                         String attributeName,
+                                                         boolean local);
+
+   /**
+    * Under the given conditions, are invocations of
+    * <code>HttpSessionActivationListener</code> callbacks allowed?
+    * 
+    * @param status the status of the session
+    * @param cause  the cause of the session notification
+    * @param attributeName value that would be passed to the <code>name</code>
+    *                      param of the <code>HttpSessionEvent</code> if
+    *                      the listener were invoked
+    *               
+    * @return <code>true</code> if the notification is allowed, 
+    *         <code>false</code> if not
+    */
+   boolean isHttpSessionActivationListenerInvocationAllowed(ClusteredSessionManagementStatus status, 
+                                                           ClusteredSessionNotificationCause cause, 
+                                                           String attributeName);
+   
+   /**
+    * Provides the policy information about the container's capabilities with
+    * respect to issuing notifications. Will be invoked by the container before
+    * the first invocation of any of the other methods in this interface.
+    * 
+    * @param capability the capability, Will not be <code>null</code>.
+    */
+   void setClusteredSessionNotificationCapability(ClusteredSessionNotificationCapability capability);
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicyBase.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicyBase.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/ClusteredSessionNotificationPolicyBase.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,29 @@
+package org.jboss.web.tomcat.service.session.notification;
+
+/**
+ * Base superclass for a {@link ClusteredSessionNotificationPolicy} implementation.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class ClusteredSessionNotificationPolicyBase 
+{
+
+   private ClusteredSessionNotificationCapability capability;
+
+   public ClusteredSessionNotificationPolicyBase()
+   {
+      super();
+   }
+
+   public void setClusteredSessionNotificationCapability(ClusteredSessionNotificationCapability capability)
+   {
+      this.capability = capability;
+   }
+
+   public ClusteredSessionNotificationCapability getClusteredSessionNotificationCapability()
+   {
+      return this.capability;
+   }
+
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/IgnoreUndeployLegacyClusteredSessionNotificationPolicy.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/IgnoreUndeployLegacyClusteredSessionNotificationPolicy.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/IgnoreUndeployLegacyClusteredSessionNotificationPolicy.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+/**
+ * Does not allow invocation of HttpSessionListener or HttpSessionAttributeListener
+ * during session expiration due to undeploy.
+ * 
+ * @author Brian Stansberry
+ */
+public class IgnoreUndeployLegacyClusteredSessionNotificationPolicy 
+   extends LegacyClusteredSessionNotificationPolicy
+{
+   /**
+    * Overrides superclass to return <code>false</code> if the cause of the
+    * notification is {@link ClusteredSessionNotificationCause.UNDEPLOY}.
+    *  
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code> and the cause of the notification is not
+    *         {@link ClusteredSessionNotificationCause.UNDEPLOY}.
+    */
+   public boolean isHttpSessionAttributeListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+   {
+      return !ClusteredSessionNotificationCause.UNDEPLOY.equals(cause) 
+               && super.isHttpSessionAttributeListenerInvocationAllowed(status, cause, attributeName, local);
+   }
+
+   /**
+    * Overrides superclass to return <code>false</code> if the cause of the
+    * notification is {@link ClusteredSessionNotificationCause.UNDEPLOY}.
+    *  
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code> and the cause of the notification is not
+    *         {@link ClusteredSessionNotificationCause.UNDEPLOY}.
+    */
+   public boolean isHttpSessionListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, boolean local)
+   {
+      return !ClusteredSessionNotificationCause.UNDEPLOY.equals(cause) 
+               && isHttpSessionListenerInvocationAllowed(status, cause, local);
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/LegacyClusteredSessionNotificationPolicy.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/LegacyClusteredSessionNotificationPolicy.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/notification/LegacyClusteredSessionNotificationPolicy.java	2008-10-16 23:12:00 UTC (rev 79611)
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session.notification;
+
+/**
+ * {@link ClusteredSessionNotificationPolicy} implementation that
+ * describes the behavior of JBoss AS releases prior to 4.2.4.
+ * 
+ * @author Brian Stansberry
+ */
+public class LegacyClusteredSessionNotificationPolicy 
+   extends ClusteredSessionNotificationPolicyBase 
+   implements ClusteredSessionNotificationPolicy
+{
+   // -------------------------------------- ClusteredSessionNotificationPolicy
+   
+   /**
+    * {@inheritDoc}
+    * 
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code>.
+    */
+   public boolean isHttpSessionAttributeListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+   {
+      return status.isLocallyUsed();
+   }
+
+   /**
+    * {@inheritDoc}
+    * 
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code>.
+    */
+   public boolean isHttpSessionBindingListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName, boolean local)
+   {
+      return status.isLocallyUsed();
+   }
+
+   /**
+    * {@inheritDoc}
+    * 
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code>.
+    */
+   public boolean isHttpSessionListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, boolean local)
+   {
+      return status.isLocallyUsed() && !ClusteredSessionNotificationCause.FAILOVER.equals(cause);
+   }
+
+   /**
+    * {@inheritDoc}
+    * 
+    * @return <code>true</code> if <code>status.isLocallyUsed()</code>
+    *         is <code>true</code>.
+    */
+   public boolean isHttpSessionActivationListenerInvocationAllowed(ClusteredSessionManagementStatus status,
+         ClusteredSessionNotificationCause cause, String attributeName)
+   {
+      return status.isLocallyUsed();
+   }
+   
+   
+
+}




More information about the jboss-cvs-commits mailing list