You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by jb...@apache.org on 2020/06/19 17:39:15 UTC

[geode] branch develop updated: GEODE-8221: Commits session data prior to sending output to browser (#5246)

This is an automated email from the ASF dual-hosted git repository.

jbarrett pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 9939cc0  GEODE-8221: Commits session data prior to sending output to browser (#5246)
9939cc0 is described below

commit 9939cc0f2f1caad051bd104a0a06a4e1737d3830
Author: Jacob Barrett <jb...@pivotal.io>
AuthorDate: Fri Jun 19 10:38:46 2020 -0700

    GEODE-8221: Commits session data prior to sending output to browser (#5246)
    
    * Refactors abstraction of CommitSessionValve.
    * Wraps Coyote OutputBuffer to commit sessions when data is sent to client.
---
 extensions/geode-modules-test/build.gradle         |  15 +-
 ...AbstractCommitSessionValveIntegrationTest.java} |  42 +-
 .../AbstractSessionValveIntegrationTest.java       |   0
 extensions/geode-modules-tomcat7/build.gradle      |   1 +
 .../CommitSessionValveIntegrationTest.java         |  57 ++
 .../catalina/Tomcat7CommitSessionOutputBuffer.java |  53 ++
 .../catalina/Tomcat7CommitSessionValve.java        |  58 ++
 .../catalina/Tomcat7DeltaSessionManager.java       |  12 +-
 .../Tomcat7CommitSessionOutputBufferTest.java      |  63 +++
 .../catalina/Tomcat7CommitSessionValveTest.java    |  98 ++++
 extensions/geode-modules-tomcat8/build.gradle      | 108 ++--
 .../CommitSessionValveIntegrationTest.java         |  57 ++
 .../modules/session/catalina/DeltaSession8.java    |   1 +
 .../catalina/Tomcat8CommitSessionOutputBuffer.java |  60 ++
 .../catalina/Tomcat8CommitSessionValve.java        |  59 ++
 .../catalina/Tomcat8DeltaSessionManager.java       |   8 +-
 .../Tomcat8CommitSessionOutputBufferTest.java      |  77 +++
 .../catalina/Tomcat8CommitSessionValveTest.java    |  98 ++++
 extensions/geode-modules-tomcat9/build.gradle      |  15 +-
 .../CommitSessionValveIntegrationTest.java         |  52 ++
 .../catalina/Tomcat9CommitSessionOutputBuffer.java |  53 ++
 .../catalina/Tomcat9CommitSessionValve.java        |  58 ++
 .../catalina/Tomcat9DeltaSessionManager.java       |   7 +-
 .../Tomcat9CommitSessionOutputBufferTest.java      |  60 ++
 .../catalina/Tomcat9CommitSessionValveTest.java    |  94 ++++
 .../catalina/AbstractCommitSessionValve.java       |  83 +++
 .../session/catalina/CommitSessionValve.java       |  69 ---
 .../session/catalina/DeltaSessionManager.java      |   9 +-
 .../session/catalina/SessionCommitter.java}        |  26 +-
 .../catalina/Tomcat6CommitSessionValve.java}       |  26 +-
 .../catalina/Tomcat6DeltaSessionManager.java       |   7 +-
 geode-assembly/geode-assembly-test/build.gradle    |   2 +
 .../integrationTest/resources/assembly_content.txt |   6 +-
 geode-core/build.gradle                            | 612 ++++++++++-----------
 34 files changed, 1534 insertions(+), 512 deletions(-)

diff --git a/extensions/geode-modules-test/build.gradle b/extensions/geode-modules-test/build.gradle
index 9b9ee95..936c5f6 100644
--- a/extensions/geode-modules-test/build.gradle
+++ b/extensions/geode-modules-test/build.gradle
@@ -20,17 +20,20 @@ import org.apache.geode.gradle.plugins.DependencyConstraints
 apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
 
 
-
 dependencies {
-  compile(platform(project(':boms:geode-all-bom')))
-  compile(project(':extensions:geode-modules')) {
+  implementation(platform(project(':boms:geode-all-bom')))
+  implementation(project(':extensions:geode-modules')) {
     // Remove everything related to Tomcat 6.x
     exclude group: 'org.apache.tomcat'
   }
-  compile(project(':geode-core'))
+  implementation(project(':geode-core'))
   implementation(project(':geode-logging'))
 
-  compile(project(':geode-junit')) {
+  implementation(project(':geode-junit')) {
+    exclude module: 'geode-core'
+  }
+
+  implementation(project(':geode-dunit')) {
     exclude module: 'geode-core'
   }
 
@@ -44,10 +47,10 @@ dependencies {
     // this version of httpunit contains very outdated xercesImpl
     exclude group: 'xerces'
   }
+  implementation('pl.pragmatists:JUnitParams')
 
   compileOnly('org.apache.tomcat:catalina-ha:' + DependencyConstraints.get('tomcat6.version')) {
     exclude module: 'annotations-api'
-    exclude module: 'coyote'
     exclude module: 'tribes'
   }
 }
diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValveIntegrationTest.java
similarity index 83%
rename from extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
rename to extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValveIntegrationTest.java
index c7e056e..7849d99 100644
--- a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValveIntegrationTest.java
@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,35 +34,34 @@ import org.apache.catalina.Manager;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.juli.logging.Log;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import org.apache.geode.cache.RegionShortcut;
 
 @RunWith(JUnitParamsRunner.class)
-public class CommitSessionValveIntegrationTest extends AbstractSessionValveIntegrationTest {
-  private Request request;
-  private Response response;
-  private TestValve testValve;
-  private CommitSessionValve commitSessionValve;
-  private DeltaSessionFacade deltaSessionFacade;
-
-  @Before
-  public void setUp() {
-    request = spy(Request.class);
-    response = spy(Response.class);
+public abstract class AbstractCommitSessionValveIntegrationTest<CommitSessionValveT extends AbstractCommitSessionValve<CommitSessionValveT>>
+    extends AbstractSessionValveIntegrationTest {
+  protected Request request;
+  protected Response response;
+  protected final TestValve testValve;
+  protected final CommitSessionValveT commitSessionValve;
+  protected DeltaSessionFacade deltaSessionFacade;
+
+  public AbstractCommitSessionValveIntegrationTest() {
     testValve = new TestValve(false);
 
-    commitSessionValve = new CommitSessionValve();
+    commitSessionValve = createCommitSessionValve();
     commitSessionValve.setNext(testValve);
   }
 
-  protected void parameterizedSetUp(RegionShortcut regionShortcut) {
+  protected abstract CommitSessionValveT createCommitSessionValve();
+
+  @Override
+  protected void parameterizedSetUp(final RegionShortcut regionShortcut) {
     super.parameterizedSetUp(regionShortcut);
 
     deltaSessionFacade = new DeltaSessionFacade(deltaSession);
-    when(request.getContext()).thenReturn(mock(Context.class));
 
     // Valve use the context to log messages
     when(deltaSessionManager.getTheContext()).thenReturn(mock(Context.class));
@@ -73,7 +71,7 @@ public class CommitSessionValveIntegrationTest extends AbstractSessionValveInteg
   @Test
   @Parameters({"REPLICATE", "PARTITION"})
   public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerDoesNotBelongToGeode(
-      RegionShortcut regionShortcut) throws IOException, ServletException {
+      final RegionShortcut regionShortcut) throws IOException, ServletException {
     parameterizedSetUp(regionShortcut);
     when(request.getContext().getManager()).thenReturn(mock(Manager.class));
 
@@ -85,7 +83,7 @@ public class CommitSessionValveIntegrationTest extends AbstractSessionValveInteg
   @Test
   @Parameters({"REPLICATE", "PARTITION"})
   public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerBelongsToGeodeButSessionDoesNotExist(
-      RegionShortcut regionShortcut) throws IOException, ServletException {
+      final RegionShortcut regionShortcut) throws IOException, ServletException {
     parameterizedSetUp(regionShortcut);
     doReturn(null).when(request).getSession(false);
     when(request.getContext().getManager()).thenReturn(deltaSessionManager);
@@ -99,7 +97,7 @@ public class CommitSessionValveIntegrationTest extends AbstractSessionValveInteg
   @Test
   @Parameters({"REPLICATE", "PARTITION"})
   public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerBelongsToGeodeAndSessionExistsButIsNotValid(
-      RegionShortcut regionShortcut) throws IOException, ServletException {
+      final RegionShortcut regionShortcut) throws IOException, ServletException {
     parameterizedSetUp(regionShortcut);
     doReturn(false).when(deltaSession).isValid();
     doReturn(deltaSessionFacade).when(request).getSession(false);
@@ -114,7 +112,7 @@ public class CommitSessionValveIntegrationTest extends AbstractSessionValveInteg
   @Test
   @Parameters({"REPLICATE", "PARTITION"})
   public void invokeShouldCallNextChainedValveAndCommitTheExistingValidSessionWhenSessionManagerBelongsToGeode(
-      RegionShortcut regionShortcut) throws IOException, ServletException {
+      final RegionShortcut regionShortcut) throws IOException, ServletException {
     parameterizedSetUp(regionShortcut);
     deltaSessionManager.addSessionToTouch(TEST_SESSION_ID);
     doReturn(deltaSessionFacade).when(request).getSession(false);
@@ -129,9 +127,9 @@ public class CommitSessionValveIntegrationTest extends AbstractSessionValveInteg
   @Test
   @Parameters({"REPLICATE", "PARTITION"})
   public void invokeShouldCommitTheExistingValidSessionWhenSessionManagerBelongsToGeodeEvenWhenTheNextChainedValveThrowsAnException(
-      RegionShortcut regionShortcut) {
+      final RegionShortcut regionShortcut) {
     parameterizedSetUp(regionShortcut);
-    TestValve exceptionValve = new TestValve(true);
+    final TestValve exceptionValve = new TestValve(true);
     commitSessionValve.setNext(exceptionValve);
     deltaSessionManager.addSessionToTouch(TEST_SESSION_ID);
     doReturn(deltaSessionFacade).when(request).getSession(false);
diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java
similarity index 100%
rename from extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java
rename to extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java
diff --git a/extensions/geode-modules-tomcat7/build.gradle b/extensions/geode-modules-tomcat7/build.gradle
index bb3e677..eb94f5b 100644
--- a/extensions/geode-modules-tomcat7/build.gradle
+++ b/extensions/geode-modules-tomcat7/build.gradle
@@ -18,6 +18,7 @@ import org.apache.geode.gradle.plugins.DependencyConstraints
  */
 
 apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
+apply from: "${rootDir}/${scriptDir}/warnings.gradle"
 
 evaluationDependsOn(":geode-core")
 
diff --git a/extensions/geode-modules-tomcat7/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java b/extensions/geode-modules-tomcat7/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
new file mode 100644
index 0000000..b64e862
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.apache.juli.logging.Log;
+import org.junit.Before;
+
+public class CommitSessionValveIntegrationTest
+    extends AbstractCommitSessionValveIntegrationTest<Tomcat7CommitSessionValve> {
+
+  @Before
+  public void setUp() {
+    final Context context = mock(Context.class);
+    doReturn(mock(Log.class)).when(context).getLogger();
+
+    request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+
+    final org.apache.coyote.Response coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setConnector(mock(Connector.class));
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+
+  @Override
+  protected Tomcat7CommitSessionValve createCommitSessionValve() {
+    return new Tomcat7CommitSessionValve();
+  }
+
+}
diff --git a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBuffer.java b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBuffer.java
new file mode 100644
index 0000000..fcf01b2
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBuffer.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Delegating {@link OutputBuffer} that commits sessions on write through. Output data is buffered
+ * ahead of this object and flushed through this interface when full or explicitly flushed.
+ */
+class Tomcat7CommitSessionOutputBuffer implements OutputBuffer {
+
+  private final SessionCommitter sessionCommitter;
+  private final OutputBuffer delegate;
+
+  public Tomcat7CommitSessionOutputBuffer(final SessionCommitter sessionCommitter,
+      final OutputBuffer delegate) {
+    this.sessionCommitter = sessionCommitter;
+    this.delegate = delegate;
+  }
+
+  @Override
+  public int doWrite(final ByteChunk chunk, final Response response) throws IOException {
+    sessionCommitter.commit();
+    return delegate.doWrite(chunk, response);
+  }
+
+  @Override
+  public long getBytesWritten() {
+    return delegate.getBytesWritten();
+  }
+
+  OutputBuffer getDelegate() {
+    return delegate;
+  }
+}
diff --git a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValve.java b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValve.java
new file mode 100644
index 0000000..f6a4839
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValve.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.lang.reflect.Field;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+
+public class Tomcat7CommitSessionValve
+    extends AbstractCommitSessionValve<Tomcat7CommitSessionValve> {
+
+  private static final Field outputBufferField;
+
+  static {
+    try {
+      outputBufferField = org.apache.coyote.Response.class.getDeclaredField("outputBuffer");
+      outputBufferField.setAccessible(true);
+    } catch (final NoSuchFieldException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Override
+  Response wrapResponse(final Response response) {
+    final org.apache.coyote.Response coyoteResponse = response.getCoyoteResponse();
+    final OutputBuffer delegateOutputBuffer = getOutputBuffer(coyoteResponse);
+    if (!(delegateOutputBuffer instanceof Tomcat7CommitSessionOutputBuffer)) {
+      final Request request = response.getRequest();
+      final OutputBuffer sessionCommitOutputBuffer =
+          new Tomcat7CommitSessionOutputBuffer(() -> commitSession(request), delegateOutputBuffer);
+      coyoteResponse.setOutputBuffer(sessionCommitOutputBuffer);
+    }
+    return response;
+  }
+
+  static OutputBuffer getOutputBuffer(final org.apache.coyote.Response coyoteResponse) {
+    try {
+      return (OutputBuffer) outputBufferField.get(coyoteResponse);
+    } catch (final IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
index e965cc5..b6d304a 100644
--- a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
@@ -20,14 +20,15 @@ import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.session.StandardSession;
-import org.apache.catalina.util.LifecycleSupport;
 
-public class Tomcat7DeltaSessionManager extends DeltaSessionManager {
+public class Tomcat7DeltaSessionManager extends DeltaSessionManager<Tomcat7CommitSessionValve> {
 
   /**
    * The <code>LifecycleSupport</code> for this component.
    */
-  protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+  @SuppressWarnings("deprecation")
+  protected org.apache.catalina.util.LifecycleSupport lifecycle =
+      new org.apache.catalina.util.LifecycleSupport(this);
 
   /**
    * Prepare for the beginning of active use of the public methods of this component. This method
@@ -165,4 +166,9 @@ public class Tomcat7DeltaSessionManager extends DeltaSessionManager {
     return new DeltaSession7(this);
   }
 
+  @Override
+  protected Tomcat7CommitSessionValve createCommitSessionValve() {
+    return new Tomcat7CommitSessionValve();
+  }
+
 }
diff --git a/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBufferTest.java b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBufferTest.java
new file mode 100644
index 0000000..20facaf
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionOutputBufferTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+public class Tomcat7CommitSessionOutputBufferTest {
+
+  final SessionCommitter sessionCommitter = mock(SessionCommitter.class);
+  final OutputBuffer delegate = mock(OutputBuffer.class);
+
+  final Tomcat7CommitSessionOutputBuffer commitSesssionOutputBuffer =
+      new Tomcat7CommitSessionOutputBuffer(sessionCommitter, delegate);
+
+  @Test
+  public void doWrite() throws IOException {
+    final ByteChunk byteChunk = new ByteChunk();
+    final Response response = new Response();
+
+    commitSesssionOutputBuffer.doWrite(byteChunk, response);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(sessionCommitter).commit();
+    inOrder.verify(delegate).doWrite(byteChunk, response);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+
+  @Test
+  public void getBytesWritten() {
+    when(delegate.getBytesWritten()).thenReturn(42L);
+
+    assertThat(commitSesssionOutputBuffer.getBytesWritten()).isEqualTo(42L);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(delegate).getBytesWritten();
+    inOrder.verifyNoMoreInteractions();
+  }
+}
diff --git a/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValveTest.java b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValveTest.java
new file mode 100644
index 0000000..c9be9b2
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValveTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.apache.geode.modules.session.catalina.Tomcat7CommitSessionValve.getOutputBuffer;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+
+public class Tomcat7CommitSessionValveTest {
+
+  private final Tomcat7CommitSessionValve valve = new Tomcat7CommitSessionValve();
+  private final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+  private Response response;
+  private org.apache.coyote.Response coyoteResponse;
+
+  @Before
+  public void before() {
+    final Connector connector = mock(Connector.class);
+
+    final Context context = mock(Context.class);
+
+    final Request request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setConnector(connector);
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+  @Test
+  public void wrappedOutputBufferForwardsToDelegate() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+  }
+
+  @Test
+  public void recycledResponseObjectDoesNotWrapAlreadyWrappedOutputBuffer() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+    response.recycle();
+    reset(outputBuffer);
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'d', 'e', 'f'});
+  }
+
+  private void wrappedOutputBufferForwardsToDelegate(final byte[] bytes) throws IOException {
+    final OutputStream outputStream =
+        valve.wrapResponse(response).getResponse().getOutputStream();
+    outputStream.write(bytes);
+    outputStream.flush();
+
+    final ArgumentCaptor<ByteChunk> byteChunk = ArgumentCaptor.forClass(ByteChunk.class);
+
+    final InOrder inOrder = inOrder(outputBuffer);
+    inOrder.verify(outputBuffer).doWrite(byteChunk.capture(), any());
+    inOrder.verifyNoMoreInteractions();
+
+    final OutputBuffer wrappedOutputBuffer = getOutputBuffer(coyoteResponse);
+    assertThat(wrappedOutputBuffer).isInstanceOf(Tomcat7CommitSessionOutputBuffer.class);
+    assertThat(((Tomcat7CommitSessionOutputBuffer) wrappedOutputBuffer).getDelegate())
+        .isNotInstanceOf(Tomcat7CommitSessionOutputBuffer.class);
+
+    assertThat(byteChunk.getValue().getBytes()).contains(bytes);
+  }
+}
diff --git a/extensions/geode-modules-tomcat8/build.gradle b/extensions/geode-modules-tomcat8/build.gradle
index 0303583..ad7f483 100644
--- a/extensions/geode-modules-tomcat8/build.gradle
+++ b/extensions/geode-modules-tomcat8/build.gradle
@@ -17,72 +17,76 @@ import org.apache.geode.gradle.plugins.DependencyConstraints
  * limitations under the License.
  */
 
+plugins {
+    id 'me.champeau.gradle.jmh' version '0.4.8'
+}
+
 apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
+apply from: "${rootDir}/${scriptDir}/warnings.gradle"
 
 evaluationDependsOn(":geode-core")
 
 
-
 dependencies {
-  compile(platform(project(':boms:geode-all-bom')))
-  distributedTestImplementation('junit:junit')
-  implementation('mx4j:mx4j')
-  distributedTestImplementation(project(':geode-logging'))
-  testImplementation('org.httpunit:httpunit')
-  testImplementation('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
-  testImplementation('org.httpunit:httpunit')
-  testImplementation('junit:junit')
-  testImplementation('org.assertj:assertj-core')
-  testImplementation('org.mockito:mockito-core')
-  testImplementation(project(':extensions:geode-modules-test'))
-  distributedTestImplementation('org.httpunit:httpunit')
-  distributedTestImplementation('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
-  distributedTestImplementation('org.springframework:spring-core')
-  compile(project(':geode-core'))
-  compile(project(':extensions:geode-modules')) {
-    exclude group: 'org.apache.tomcat'
-  }
-  testImplementation(project(':extensions:geode-modules')) {
-    exclude group: 'org.apache.tomcat'
-  }
+    compile(platform(project(':boms:geode-all-bom')))
+    distributedTestImplementation('junit:junit')
+    implementation('mx4j:mx4j')
+    distributedTestImplementation(project(':geode-logging'))
+    testImplementation('org.httpunit:httpunit')
+    testImplementation('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
+    testImplementation('org.httpunit:httpunit')
+    testImplementation('junit:junit')
+    testImplementation('org.assertj:assertj-core')
+    testImplementation('org.mockito:mockito-core')
+    testImplementation(project(':extensions:geode-modules-test'))
+    distributedTestImplementation('org.httpunit:httpunit')
+    distributedTestImplementation('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
+    distributedTestImplementation('org.springframework:spring-core')
+    compile(project(':geode-core'))
+    compile(project(':extensions:geode-modules')) {
+        exclude group: 'org.apache.tomcat'
+    }
+    testImplementation(project(':extensions:geode-modules')) {
+        exclude group: 'org.apache.tomcat'
+    }
 
 
-  implementation('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat8.version')) {
-    exclude module: 'tomcat-annotations-api'
-    exclude module: 'tomcat-servlet-api'
-  }
-  implementation('org.apache.tomcat:tomcat-coyote:' + DependencyConstraints.get('tomcat8.version')) {
-    exclude module: 'tomcat-servlet-api'
-  }
-  implementation('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat8.version'))
-  implementation('javax.servlet:javax.servlet-api')
+    implementation('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat8.version')) {
+        exclude module: 'tomcat-annotations-api'
+        exclude module: 'tomcat-servlet-api'
+    }
+    implementation('org.apache.tomcat:tomcat-coyote:' + DependencyConstraints.get('tomcat8.version')) {
+        exclude module: 'tomcat-servlet-api'
+    }
+    implementation('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat8.version'))
+    implementation('javax.servlet:javax.servlet-api')
 
-  integrationTestImplementation(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
-  integrationTestImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  integrationTestImplementation(project(':extensions:geode-modules-test'))
-  integrationTestRuntimeOnly('xerces:xercesImpl')
-  integrationTestRuntimeOnly('javax.annotation:javax.annotation-api')
+    integrationTestImplementation(project(':geode-dunit')) {
+        exclude module: 'geode-core'
+    }
+    integrationTestImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    integrationTestImplementation(project(':extensions:geode-modules-test'))
+    integrationTestRuntimeOnly('xerces:xercesImpl')
+    integrationTestRuntimeOnly('javax.annotation:javax.annotation-api')
 
-  distributedTestImplementation(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
+    distributedTestImplementation(project(':geode-dunit')) {
+        exclude module: 'geode-core'
+    }
 
-  distributedTestImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  distributedTestImplementation(project(':extensions:geode-modules-test'))
+    distributedTestImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    distributedTestImplementation(project(':extensions:geode-modules-test'))
 
-  distributedTestRuntimeOnly('xerces:xercesImpl')
-  distributedTestRuntimeOnly('javax.annotation:javax.annotation-api')
+    distributedTestRuntimeOnly('xerces:xercesImpl')
+    distributedTestRuntimeOnly('javax.annotation:javax.annotation-api')
 }
 
 eclipse.classpath.file {
-  whenMerged { classpath ->
-    classpath.entries.removeAll { entry -> entry.path.contains('geode-modules/build')}
-  }
+    whenMerged { classpath ->
+        classpath.entries.removeAll { entry -> entry.path.contains('geode-modules/build') }
+    }
 }
 
diff --git a/extensions/geode-modules-tomcat8/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java b/extensions/geode-modules-tomcat8/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
new file mode 100644
index 0000000..79df936
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.apache.juli.logging.Log;
+import org.junit.Before;
+
+public class CommitSessionValveIntegrationTest
+    extends AbstractCommitSessionValveIntegrationTest<Tomcat8CommitSessionValve> {
+
+  @Before
+  public void setUp() {
+    final Context context = mock(Context.class);
+    doReturn(mock(Log.class)).when(context).getLogger();
+
+    request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+
+    final org.apache.coyote.Response coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setConnector(mock(Connector.class));
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+
+  @Override
+  protected Tomcat8CommitSessionValve createCommitSessionValve() {
+    return new Tomcat8CommitSessionValve();
+  }
+
+}
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
index a17fdde..c2ea5c5 100644
--- a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
+++ b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
@@ -12,6 +12,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package org.apache.geode.modules.session.catalina;
 
 import org.apache.catalina.Manager;
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBuffer.java b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBuffer.java
new file mode 100644
index 0000000..4197b59
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBuffer.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Delegating {@link OutputBuffer} that commits sessions on write through. Output data is buffered
+ * ahead of this object and flushed through this interface when full or explicitly flushed.
+ */
+class Tomcat8CommitSessionOutputBuffer implements OutputBuffer {
+
+  private final SessionCommitter sessionCommitter;
+  private final OutputBuffer delegate;
+
+  public Tomcat8CommitSessionOutputBuffer(final SessionCommitter sessionCommitter,
+      final OutputBuffer delegate) {
+    this.sessionCommitter = sessionCommitter;
+    this.delegate = delegate;
+  }
+
+  @Deprecated
+  @Override
+  public int doWrite(final ByteChunk chunk) throws IOException {
+    sessionCommitter.commit();
+    return delegate.doWrite(chunk);
+  }
+
+  @Override
+  public int doWrite(final ByteBuffer chunk) throws IOException {
+    sessionCommitter.commit();
+    return delegate.doWrite(chunk);
+  }
+
+  @Override
+  public long getBytesWritten() {
+    return delegate.getBytesWritten();
+  }
+
+  OutputBuffer getDelegate() {
+    return delegate;
+  }
+}
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValve.java b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValve.java
new file mode 100644
index 0000000..fe5f65a
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValve.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.lang.reflect.Field;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+
+public class Tomcat8CommitSessionValve
+    extends AbstractCommitSessionValve<Tomcat8CommitSessionValve> {
+
+  private static final Field outputBufferField;
+
+  static {
+    try {
+      outputBufferField = org.apache.coyote.Response.class.getDeclaredField("outputBuffer");
+      outputBufferField.setAccessible(true);
+    } catch (final NoSuchFieldException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Override
+  Response wrapResponse(final Response response) {
+    final org.apache.coyote.Response coyoteResponse = response.getCoyoteResponse();
+    final OutputBuffer delegateOutputBuffer = getOutputBuffer(coyoteResponse);
+    if (!(delegateOutputBuffer instanceof Tomcat8CommitSessionOutputBuffer)) {
+      final Request request = response.getRequest();
+      final OutputBuffer sessionCommitOutputBuffer =
+          new Tomcat8CommitSessionOutputBuffer(() -> commitSession(request), delegateOutputBuffer);
+      coyoteResponse.setOutputBuffer(sessionCommitOutputBuffer);
+    }
+    return response;
+  }
+
+  static OutputBuffer getOutputBuffer(final org.apache.coyote.Response coyoteResponse) {
+    try {
+      return (OutputBuffer) outputBufferField.get(coyoteResponse);
+    } catch (final IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+}
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
index 8a32ac0..573bf43 100644
--- a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
@@ -12,6 +12,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
+
 package org.apache.geode.modules.session.catalina;
 
 import java.io.IOException;
@@ -22,7 +23,7 @@ import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Pipeline;
 import org.apache.catalina.session.StandardSession;
 
-public class Tomcat8DeltaSessionManager extends DeltaSessionManager {
+public class Tomcat8DeltaSessionManager extends DeltaSessionManager<Tomcat8CommitSessionValve> {
 
   /**
    * Prepare for the beginning of active use of the public methods of this component. This method
@@ -138,6 +139,11 @@ public class Tomcat8DeltaSessionManager extends DeltaSessionManager {
   }
 
   @Override
+  protected Tomcat8CommitSessionValve createCommitSessionValve() {
+    return new Tomcat8CommitSessionValve();
+  }
+
+  @Override
   public Context getTheContext() {
     return getContext();
   }
diff --git a/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBufferTest.java b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBufferTest.java
new file mode 100644
index 0000000..4efc77b
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionOutputBufferTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+public class Tomcat8CommitSessionOutputBufferTest {
+
+  final SessionCommitter sessionCommitter = mock(SessionCommitter.class);
+  final OutputBuffer delegate = mock(OutputBuffer.class);
+
+  final Tomcat8CommitSessionOutputBuffer commitSesssionOutputBuffer =
+      new Tomcat8CommitSessionOutputBuffer(sessionCommitter, delegate);
+
+  /**
+   * @deprecated Remove when {@link OutputBuffer} drops this method.
+   */
+  @Deprecated
+  @Test
+  public void doWrite() throws IOException {
+    final ByteChunk byteChunk = new ByteChunk();
+
+    commitSesssionOutputBuffer.doWrite(byteChunk);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(sessionCommitter).commit();
+    inOrder.verify(delegate).doWrite(byteChunk);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void testDoWrite() throws IOException {
+    final ByteBuffer byteBuffer = ByteBuffer.allocate(0);
+
+    commitSesssionOutputBuffer.doWrite(byteBuffer);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(sessionCommitter).commit();
+    inOrder.verify(delegate).doWrite(byteBuffer);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void getBytesWritten() {
+    when(delegate.getBytesWritten()).thenReturn(42L);
+
+    assertThat(commitSesssionOutputBuffer.getBytesWritten()).isEqualTo(42L);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(delegate).getBytesWritten();
+    inOrder.verifyNoMoreInteractions();
+  }
+}
diff --git a/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValveTest.java b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValveTest.java
new file mode 100644
index 0000000..5cc2f0a
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValveTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.apache.geode.modules.session.catalina.Tomcat8CommitSessionValve.getOutputBuffer;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+
+public class Tomcat8CommitSessionValveTest {
+
+  private final Tomcat8CommitSessionValve valve = new Tomcat8CommitSessionValve();
+  private final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+  private Response response;
+  private org.apache.coyote.Response coyoteResponse;
+
+  @Before
+  public void before() {
+    final Connector connector = mock(Connector.class);
+
+    final Context context = mock(Context.class);
+
+    final Request request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setConnector(connector);
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+  @Test
+  public void wrappedOutputBufferForwardsToDelegate() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+  }
+
+  @Test
+  public void recycledResponseObjectDoesNotWrapAlreadyWrappedOutputBuffer() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+    response.recycle();
+    reset(outputBuffer);
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'d', 'e', 'f'});
+  }
+
+  private void wrappedOutputBufferForwardsToDelegate(final byte[] bytes) throws IOException {
+    final OutputStream outputStream =
+        valve.wrapResponse(response).getResponse().getOutputStream();
+    outputStream.write(bytes);
+    outputStream.flush();
+
+    final ArgumentCaptor<ByteBuffer> byteBuffer = ArgumentCaptor.forClass(ByteBuffer.class);
+
+    final InOrder inOrder = inOrder(outputBuffer);
+    inOrder.verify(outputBuffer).doWrite(byteBuffer.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    final OutputBuffer wrappedOutputBuffer = getOutputBuffer(coyoteResponse);
+    assertThat(wrappedOutputBuffer).isInstanceOf(Tomcat8CommitSessionOutputBuffer.class);
+    assertThat(((Tomcat8CommitSessionOutputBuffer) wrappedOutputBuffer).getDelegate())
+        .isNotInstanceOf(Tomcat8CommitSessionOutputBuffer.class);
+
+    assertThat(byteBuffer.getValue().array()).contains(bytes);
+  }
+
+}
diff --git a/extensions/geode-modules-tomcat9/build.gradle b/extensions/geode-modules-tomcat9/build.gradle
index a8763a8..dea8c30 100644
--- a/extensions/geode-modules-tomcat9/build.gradle
+++ b/extensions/geode-modules-tomcat9/build.gradle
@@ -18,19 +18,16 @@ import org.apache.geode.gradle.plugins.DependencyConstraints
  */
 
 apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
+apply from: "${rootDir}/${scriptDir}/warnings.gradle"
 
 evaluationDependsOn(":geode-core")
 
 
-
 dependencies {
   compile(platform(project(':boms:geode-all-bom')))
   api(project(':extensions:geode-modules')) {
     exclude group: 'org.apache.tomcat'
   }
-//  testImplementation(project(':extensions:geode-modules')) {
-//    exclude group: 'org.apache.tomcat'
-//  }
 
   implementation('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat9.version')) {
     exclude module: 'tomcat-annotations-api'
@@ -40,7 +37,7 @@ dependencies {
     exclude module: 'tomcat-servlet-api'
   }
   implementation('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat9.version'))
-  implementation('javax.servlet:javax.servlet-api:' + '3.1.0')
+  implementation('javax.servlet:javax.servlet-api:4.0.1')
 
   testImplementation('org.httpunit:httpunit')
   testImplementation('junit:junit')
@@ -48,15 +45,15 @@ dependencies {
   testImplementation('org.mockito:mockito-core')
   testImplementation(project(':extensions:geode-modules-test'))
 
-  distributedTestImplementation(project(':extensions:geode-modules-test'))
+  integrationTestImplementation(project(':extensions:geode-modules-test'))
+  integrationTestImplementation('junit:junit')
+  integrationTestImplementation('org.mockito:mockito-core')
 
-  distributedTestRuntimeOnly('xerces:xercesImpl')
-  distributedTestRuntimeOnly('javax.annotation:javax.annotation-api')
 }
 
 eclipse.classpath.file {
   whenMerged { classpath ->
-    classpath.entries.removeAll { entry -> entry.path.contains('geode-modules/build')}
+    classpath.entries.removeAll { entry -> entry.path.contains('geode-modules/build') }
   }
 }
 
diff --git a/extensions/geode-modules-tomcat9/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java b/extensions/geode-modules-tomcat9/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
new file mode 100644
index 0000000..c43729a
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.modules.session.catalina;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.apache.juli.logging.Log;
+import org.junit.Before;
+
+public class CommitSessionValveIntegrationTest
+    extends AbstractCommitSessionValveIntegrationTest<Tomcat9CommitSessionValve> {
+
+  @Before
+  public void setUp() {
+    final Context context = mock(Context.class);
+    doReturn(mock(Log.class)).when(context).getLogger();
+
+    request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+
+    final org.apache.coyote.Response coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+  @Override
+  protected Tomcat9CommitSessionValve createCommitSessionValve() {
+    return new Tomcat9CommitSessionValve();
+  }
+}
diff --git a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBuffer.java b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBuffer.java
new file mode 100644
index 0000000..4e4600b
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBuffer.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.OutputBuffer;
+
+
+/**
+ * Delegating {@link OutputBuffer} that commits sessions on write through. Output data is buffered
+ * ahead of this object and flushed through this interface when full or explicitly flushed.
+ */
+class Tomcat9CommitSessionOutputBuffer implements OutputBuffer {
+
+  private final SessionCommitter sessionCommitter;
+  private final OutputBuffer delegate;
+
+  public Tomcat9CommitSessionOutputBuffer(final SessionCommitter sessionCommitter,
+      final OutputBuffer delegate) {
+    this.sessionCommitter = sessionCommitter;
+    this.delegate = delegate;
+  }
+
+  @Override
+  public int doWrite(final ByteBuffer chunk) throws IOException {
+    sessionCommitter.commit();
+    return delegate.doWrite(chunk);
+  }
+
+  @Override
+  public long getBytesWritten() {
+    return delegate.getBytesWritten();
+  }
+
+  OutputBuffer getDelegate() {
+    return delegate;
+  }
+}
diff --git a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValve.java b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValve.java
new file mode 100644
index 0000000..925b0d2
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValve.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import java.lang.reflect.Field;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+
+public class Tomcat9CommitSessionValve
+    extends AbstractCommitSessionValve<Tomcat9CommitSessionValve> {
+
+  private static final Field outputBufferField;
+
+  static {
+    try {
+      outputBufferField = org.apache.coyote.Response.class.getDeclaredField("outputBuffer");
+      outputBufferField.setAccessible(true);
+    } catch (final NoSuchFieldException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Override
+  Response wrapResponse(final Response response) {
+    final org.apache.coyote.Response coyoteResponse = response.getCoyoteResponse();
+    final OutputBuffer delegateOutputBuffer = getOutputBuffer(coyoteResponse);
+    if (!(delegateOutputBuffer instanceof Tomcat9CommitSessionOutputBuffer)) {
+      final Request request = response.getRequest();
+      final OutputBuffer sessionCommitOutputBuffer =
+          new Tomcat9CommitSessionOutputBuffer(() -> commitSession(request), delegateOutputBuffer);
+      coyoteResponse.setOutputBuffer(sessionCommitOutputBuffer);
+    }
+    return response;
+  }
+
+  static OutputBuffer getOutputBuffer(final org.apache.coyote.Response coyoteResponse) {
+    try {
+      return (OutputBuffer) outputBufferField.get(coyoteResponse);
+    } catch (final IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
index ac9ac2c..5c7e47d 100644
--- a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
@@ -22,7 +22,7 @@ import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Pipeline;
 import org.apache.catalina.session.StandardSession;
 
-public class Tomcat9DeltaSessionManager extends DeltaSessionManager {
+public class Tomcat9DeltaSessionManager extends DeltaSessionManager<Tomcat9CommitSessionValve> {
 
   /**
    * Prepare for the beginning of active use of the public methods of this component. This method
@@ -138,6 +138,11 @@ public class Tomcat9DeltaSessionManager extends DeltaSessionManager {
   }
 
   @Override
+  protected Tomcat9CommitSessionValve createCommitSessionValve() {
+    return new Tomcat9CommitSessionValve();
+  }
+
+  @Override
   public Context getTheContext() {
     return getContext();
   }
diff --git a/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBufferTest.java b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBufferTest.java
new file mode 100644
index 0000000..0ec3a00
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionOutputBufferTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.OutputBuffer;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+public class Tomcat9CommitSessionOutputBufferTest {
+
+  final SessionCommitter sessionCommitter = mock(SessionCommitter.class);
+  final OutputBuffer delegate = mock(OutputBuffer.class);
+
+  final Tomcat9CommitSessionOutputBuffer commitSesssionOutputBuffer =
+      new Tomcat9CommitSessionOutputBuffer(sessionCommitter, delegate);
+
+  @Test
+  public void testDoWrite() throws IOException {
+    final ByteBuffer byteBuffer = ByteBuffer.allocate(0);
+
+    commitSesssionOutputBuffer.doWrite(byteBuffer);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(sessionCommitter).commit();
+    inOrder.verify(delegate).doWrite(byteBuffer);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void getBytesWritten() {
+    when(delegate.getBytesWritten()).thenReturn(42L);
+
+    assertThat(commitSesssionOutputBuffer.getBytesWritten()).isEqualTo(42L);
+
+    final InOrder inOrder = inOrder(sessionCommitter, delegate);
+    inOrder.verify(delegate).getBytesWritten();
+    inOrder.verifyNoMoreInteractions();
+  }
+}
diff --git a/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValveTest.java b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValveTest.java
new file mode 100644
index 0000000..32095a2
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValveTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.modules.session.catalina;
+
+import static org.apache.geode.modules.session.catalina.Tomcat9CommitSessionValve.getOutputBuffer;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.OutputBuffer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+
+public class Tomcat9CommitSessionValveTest {
+
+  private final Tomcat9CommitSessionValve valve = new Tomcat9CommitSessionValve();
+  private final OutputBuffer outputBuffer = mock(OutputBuffer.class);
+  private Response response;
+  private org.apache.coyote.Response coyoteResponse;
+
+  @Before
+  public void before() {
+    final Context context = mock(Context.class);
+
+    final Request request = mock(Request.class);
+    doReturn(context).when(request).getContext();
+
+    coyoteResponse = new org.apache.coyote.Response();
+    coyoteResponse.setOutputBuffer(outputBuffer);
+
+    response = new Response();
+    response.setRequest(request);
+    response.setCoyoteResponse(coyoteResponse);
+  }
+
+  @Test
+  public void wrappedOutputBufferForwardsToDelegate() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+  }
+
+  @Test
+  public void recycledResponseObjectDoesNotWrapAlreadyWrappedOutputBuffer() throws IOException {
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'a', 'b', 'c'});
+    response.recycle();
+    reset(outputBuffer);
+    wrappedOutputBufferForwardsToDelegate(new byte[] {'d', 'e', 'f'});
+  }
+
+  private void wrappedOutputBufferForwardsToDelegate(final byte[] bytes) throws IOException {
+    final OutputStream outputStream =
+        valve.wrapResponse(response).getResponse().getOutputStream();
+    outputStream.write(bytes);
+    outputStream.flush();
+
+    final ArgumentCaptor<ByteBuffer> byteBuffer = ArgumentCaptor.forClass(ByteBuffer.class);
+
+    final InOrder inOrder = inOrder(outputBuffer);
+    inOrder.verify(outputBuffer).doWrite(byteBuffer.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    final OutputBuffer wrappedOutputBuffer = getOutputBuffer(coyoteResponse);
+    assertThat(wrappedOutputBuffer).isInstanceOf(Tomcat9CommitSessionOutputBuffer.class);
+    assertThat(((Tomcat9CommitSessionOutputBuffer) wrappedOutputBuffer).getDelegate())
+        .isNotInstanceOf(Tomcat9CommitSessionOutputBuffer.class);
+
+    assertThat(byteBuffer.getValue().array()).contains(bytes);
+  }
+
+}
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValve.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValve.java
new file mode 100644
index 0000000..fece1af
--- /dev/null
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValve.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.modules.session.catalina;
+
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Manager;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public abstract class AbstractCommitSessionValve<SelfT extends AbstractCommitSessionValve<?>>
+    extends ValveBase {
+
+  private static final Log log = LogFactory.getLog(AbstractCommitSessionValve.class);
+
+  protected static final String info =
+      "org.apache.geode.modules.session.catalina.CommitSessionValve/1.0";
+
+  AbstractCommitSessionValve() {
+    log.info("Initialized");
+  }
+
+  @Override
+  public void invoke(final Request request, final Response response)
+      throws IOException, ServletException {
+    try {
+      getNext().invoke(request, wrapResponse(response));
+    } finally {
+      commitSession(request);
+    }
+  }
+
+  /**
+   * Commit session only if DeltaSessionManager is in place.
+   *
+   * @param request to commit session from.
+   */
+  protected static <SelfT extends AbstractCommitSessionValve<?>> void commitSession(
+      final Request request) {
+    final Context context = request.getContext();
+    final Manager manager = context.getManager();
+    if (manager instanceof DeltaSessionManager) {
+      final DeltaSessionFacade session = (DeltaSessionFacade) request.getSession(false);
+      if (session != null) {
+        final DeltaSessionManager<SelfT> deltaSessionManager = uncheckedCast(manager);
+        if (session.isValid()) {
+          deltaSessionManager.removeTouchedSession(session.getId());
+          session.commit();
+          if (log.isDebugEnabled()) {
+            log.debug(session + ": Committed.");
+          }
+        } else {
+          if (log.isDebugEnabled()) {
+            log.debug(session + ": Not valid so not committing.");
+          }
+        }
+      }
+    }
+  }
+
+  abstract Response wrapResponse(final Response response);
+
+}
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/CommitSessionValve.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/CommitSessionValve.java
deleted file mode 100644
index 1da58b7..0000000
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/CommitSessionValve.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.geode.modules.session.catalina;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-
-import org.apache.catalina.Manager;
-import org.apache.catalina.connector.Request;
-import org.apache.catalina.connector.Response;
-import org.apache.catalina.valves.ValveBase;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
-public class CommitSessionValve extends ValveBase {
-
-  private static final Log log = LogFactory.getLog(CommitSessionValve.class);
-
-  protected static final String info =
-      "org.apache.geode.modules.session.catalina.CommitSessionValve/1.0";
-
-  CommitSessionValve() {
-    log.info("Initialized");
-  }
-
-  @Override
-  public void invoke(Request request, Response response) throws IOException, ServletException {
-    // Get the Manager
-    Manager manager = request.getContext().getManager();
-    DeltaSessionFacade session;
-
-    // Invoke the next Valve
-    try {
-      getNext().invoke(request, response);
-    } finally {
-      // Commit and if the correct Manager was found
-      if (manager instanceof DeltaSessionManager) {
-        session = (DeltaSessionFacade) request.getSession(false);
-        DeltaSessionManager dsm = ((DeltaSessionManager) manager);
-        if (session != null) {
-          if (session.isValid()) {
-            dsm.removeTouchedSession(session.getId());
-            session.commit();
-            if (dsm.getTheContext().getLogger().isDebugEnabled()) {
-              dsm.getTheContext().getLogger().debug(session + ": Committed.");
-            }
-          } else {
-            if (dsm.getTheContext().getLogger().isDebugEnabled()) {
-              dsm.getTheContext().getLogger().debug(session + ": Not valid so not committing.");
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
index a0ffca9..c1dddc6 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
@@ -65,7 +65,8 @@ import org.apache.geode.modules.util.ContextMapper;
 import org.apache.geode.modules.util.RegionConfiguration;
 import org.apache.geode.modules.util.RegionHelper;
 
-public abstract class DeltaSessionManager extends ManagerBase
+public abstract class DeltaSessionManager<CommitSessionValveT extends AbstractCommitSessionValve>
+    extends ManagerBase
     implements Lifecycle, PropertyChangeListener, SessionManager {
 
   static final String catalinaBaseSystemProperty = "catalina.base";
@@ -93,7 +94,7 @@ public abstract class DeltaSessionManager extends ManagerBase
 
   private Valve jvmRouteBinderValve;
 
-  private Valve commitSessionValve;
+  private CommitSessionValveT commitSessionValve;
 
   private SessionCache sessionCache;
 
@@ -603,10 +604,12 @@ public abstract class DeltaSessionManager extends ManagerBase
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Registering CommitSessionValve");
     }
-    commitSessionValve = new CommitSessionValve();
+    commitSessionValve = createCommitSessionValve();
     getPipeline().addValve(commitSessionValve);
   }
 
+  protected abstract CommitSessionValveT createCommitSessionValve();
+
   protected void unregisterCommitSessionValve() {
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Unregistering CommitSessionValve");
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/SessionCommitter.java
similarity index 60%
copy from extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
copy to extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/SessionCommitter.java
index a17fdde..5af9aa2 100644
--- a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/SessionCommitter.java
@@ -12,28 +12,16 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.modules.session.catalina;
-
-import org.apache.catalina.Manager;
 
+package org.apache.geode.modules.session.catalina;
 
-@SuppressWarnings("serial")
-public class DeltaSession8 extends DeltaSession {
-  /**
-   * Construct a new <code>Session</code> associated with no <code>Manager</code>. The
-   * <code>Manager</code> will be assigned later using {@link #setOwner(Object)}.
-   */
-  @SuppressWarnings("unused")
-  public DeltaSession8() {
-    super();
-  }
+/**
+ * Lambda interface for committing session data.
+ */
+interface SessionCommitter {
 
   /**
-   * Construct a new Session associated with the specified Manager.
-   *
-   * @param manager The manager with which this Session is associated
+   * Invoked to commit session data.
    */
-  DeltaSession8(Manager manager) {
-    super(manager);
-  }
+  void commit();
 }
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6CommitSessionValve.java
similarity index 59%
copy from extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
copy to extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6CommitSessionValve.java
index a17fdde..b27fe65 100644
--- a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession8.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6CommitSessionValve.java
@@ -12,28 +12,16 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.modules.session.catalina;
 
-import org.apache.catalina.Manager;
+package org.apache.geode.modules.session.catalina;
 
+import org.apache.catalina.connector.Response;
 
-@SuppressWarnings("serial")
-public class DeltaSession8 extends DeltaSession {
-  /**
-   * Construct a new <code>Session</code> associated with no <code>Manager</code>. The
-   * <code>Manager</code> will be assigned later using {@link #setOwner(Object)}.
-   */
-  @SuppressWarnings("unused")
-  public DeltaSession8() {
-    super();
-  }
+@Deprecated
+public final class Tomcat6CommitSessionValve extends AbstractCommitSessionValve {
 
-  /**
-   * Construct a new Session associated with the specified Manager.
-   *
-   * @param manager The manager with which this Session is associated
-   */
-  DeltaSession8(Manager manager) {
-    super(manager);
+  @Override
+  protected Response wrapResponse(Response response) {
+    return response;
   }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6DeltaSessionManager.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6DeltaSessionManager.java
index 555f09e..1abceb9 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6DeltaSessionManager.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/Tomcat6DeltaSessionManager.java
@@ -21,7 +21,7 @@ import org.apache.catalina.util.LifecycleSupport;
  * @deprecated Tomcat 6 has reached its end of life and support for Tomcat 6 will be removed
  *             from a future Geode release.
  */
-public class Tomcat6DeltaSessionManager extends DeltaSessionManager {
+public class Tomcat6DeltaSessionManager extends DeltaSessionManager<Tomcat6CommitSessionValve> {
 
   /**
    * The <code>LifecycleSupport</code> for this component.
@@ -131,4 +131,9 @@ public class Tomcat6DeltaSessionManager extends DeltaSessionManager {
   public void removeLifecycleListener(LifecycleListener listener) {
     this.lifecycle.removeLifecycleListener(listener);
   }
+
+  @Override
+  protected Tomcat6CommitSessionValve createCommitSessionValve() {
+    return new Tomcat6CommitSessionValve();
+  }
 }
diff --git a/geode-assembly/geode-assembly-test/build.gradle b/geode-assembly/geode-assembly-test/build.gradle
index 40c01cb..a53fdb1 100755
--- a/geode-assembly/geode-assembly-test/build.gradle
+++ b/geode-assembly/geode-assembly-test/build.gradle
@@ -25,9 +25,11 @@ dependencies {
   compileOnly(project(':extensions:geode-modules-test'))
   compileOnly(project(':geode-core'))
   compileOnly(project(':geode-pulse'))
+  compileOnly(project(':geode-junit'))
   implementation(project(':geode-logging'))
   implementation(project(':geode-membership'))
   implementation('javax.servlet:javax.servlet-api')
+  implementation('org.apache.commons:commons-lang3')
   compileOnly(project(':geode-tcp-server'))
   compileOnly('com.fasterxml.jackson.core:jackson-databind')
   compileOnly('commons-io:commons-io')
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 7ed9ce1..bec9cce 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -837,7 +837,11 @@ javadoc/org/apache/geode/modules/session/catalina/AbstractCacheLifecycleListener
 javadoc/org/apache/geode/modules/session/catalina/AbstractSessionCache.html
 javadoc/org/apache/geode/modules/session/catalina/ClientServerCacheLifecycleListener.html
 javadoc/org/apache/geode/modules/session/catalina/ClientServerSessionCache.html
-javadoc/org/apache/geode/modules/session/catalina/CommitSessionValve.html
+javadoc/org/apache/geode/modules/session/catalina/AbstractCommitSessionValve.html
+javadoc/org/apache/geode/modules/session/catalina/Tomcat6CommitSessionValve.html
+javadoc/org/apache/geode/modules/session/catalina/Tomcat7CommitSessionValve.html
+javadoc/org/apache/geode/modules/session/catalina/Tomcat8CommitSessionValve.html
+javadoc/org/apache/geode/modules/session/catalina/Tomcat9CommitSessionValve.html
 javadoc/org/apache/geode/modules/session/catalina/DeltaSession.html
 javadoc/org/apache/geode/modules/session/catalina/DeltaSession7.html
 javadoc/org/apache/geode/modules/session/catalina/DeltaSession8.html
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index 6822133..1f3c407 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -16,7 +16,7 @@
  */
 
 plugins {
-  id 'me.champeau.gradle.jmh' version '0.4.8'
+    id 'me.champeau.gradle.jmh' version '0.4.8'
 }
 
 apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
@@ -26,95 +26,95 @@ apply from: "${project.projectDir}/../gradle/publish-java.gradle"
 apply from: "${project.projectDir}/../gradle/pmd.gradle"
 
 sourceSets {
-  jca {
-    compileClasspath += configurations.compileClasspath
-    runtimeClasspath += configurations.runtimeClasspath
-  }
+    jca {
+        compileClasspath += configurations.compileClasspath
+        runtimeClasspath += configurations.runtimeClasspath
+    }
 }
 
 idea {
-  module {
-    testSourceDirs += project.tasks.generateIntegrationTestGrammarSource.outputs.files
-    testSourceDirs += project.tasks.generateDistributedTestGrammarSource.outputs.files
-    testSourceDirs += project.tasks.generatePerformanceTestGrammarSource.outputs.files
-    testSourceDirs += project.tasks.generateUpgradeTestGrammarSource.outputs.files
-  }
+    module {
+        testSourceDirs += project.tasks.generateIntegrationTestGrammarSource.outputs.files
+        testSourceDirs += project.tasks.generateDistributedTestGrammarSource.outputs.files
+        testSourceDirs += project.tasks.generatePerformanceTestGrammarSource.outputs.files
+        testSourceDirs += project.tasks.generateUpgradeTestGrammarSource.outputs.files
+    }
 }
 
 def generatedResources = "$buildDir/generated-resources/main"
 
 sourceSets {
-  main {
-    output.dir(generatedResources, builtBy: 'createVersionPropertiesFile')
-  }
-  test {
-    output.dir(generatedResources, builtBy: 'createVersionPropertiesFile')
-  }
+    main {
+        output.dir(generatedResources, builtBy: 'createVersionPropertiesFile')
+    }
+    test {
+        output.dir(generatedResources, builtBy: 'createVersionPropertiesFile')
+    }
 }
 
 sourceSets {
 // This works around resource-look up between integrationTest and test source-sets.
 // See GEODE-5803 / GEODE-5882
-  test.resources.srcDirs.each { testResourceSrc ->
-    integrationTest.resources.srcDir {
-      testResourceSrc
+    test.resources.srcDirs.each { testResourceSrc ->
+        integrationTest.resources.srcDir {
+            testResourceSrc
+        }
     }
-  }
 }
 
 
 // Creates the version properties file and writes it to the classes dir
 task createVersionPropertiesFile(dependsOn: ':writeBuildInfo') {
 
-  def propertiesFile = file(generatedResources + "/org/apache/geode/internal/GemFireVersion.properties")
-  def scmInfoFile = rootProject.tasks.writeBuildInfo.outputs.files
+    def propertiesFile = file(generatedResources + "/org/apache/geode/internal/GemFireVersion.properties")
+    def scmInfoFile = rootProject.tasks.writeBuildInfo.outputs.files
 
-  inputs.files {
-    scmInfoFile
-  }
-  outputs.files {
-    propertiesFile
-  }
+    inputs.files {
+        scmInfoFile
+    }
+    outputs.files {
+        propertiesFile
+    }
 
 
-  doLast {
-    def scmInfo = new Properties()
-    new FileInputStream(scmInfoFile.singleFile).withStream { fis ->
-      scmInfo.load(fis)
-    }
+    doLast {
+        def scmInfo = new Properties()
+        new FileInputStream(scmInfoFile.singleFile).withStream { fis ->
+            scmInfo.load(fis)
+        }
 
-    def props = [
-        "Product-Name"      : productName,
-        "Product-Version"   : version,
-        "Build-Id"          : "${System.env.USER} ${buildId}".toString(),
-        "Build-Date"        : new Date().format('yyyy-MM-dd HH:mm:ss Z'),
-        "Build-Platform"    : "${System.properties['os.name']} ${System.properties['os.version']} ${System.properties['os.arch']}".toString(),
-        "Build-Java-Vendor" : System.properties['java.vendor'],
-        "Build-Java-Version": System.properties['java.version']
-    ] as Properties
-    props.putAll(scmInfo)
-
-    propertiesFile.getParentFile().mkdirs()
-    new FileOutputStream(propertiesFile).withStream { fos ->
-      props.store(fos, '')
+        def props = [
+                "Product-Name"      : productName,
+                "Product-Version"   : version,
+                "Build-Id"          : "${System.env.USER} ${buildId}".toString(),
+                "Build-Date"        : new Date().format('yyyy-MM-dd HH:mm:ss Z'),
+                "Build-Platform"    : "${System.properties['os.name']} ${System.properties['os.version']} ${System.properties['os.arch']}".toString(),
+                "Build-Java-Vendor" : System.properties['java.vendor'],
+                "Build-Java-Version": System.properties['java.version']
+        ] as Properties
+        props.putAll(scmInfo)
+
+        propertiesFile.getParentFile().mkdirs()
+        new FileOutputStream(propertiesFile).withStream { fos ->
+            props.store(fos, '')
+        }
     }
-  }
 }
 
 ext.moduleName = group + '.core'
 
 jar {
 
-  from sourceSets.main.output
-  from sourceSets.jca.output
+    from sourceSets.main.output
+    from sourceSets.jca.output
 
-  exclude 'org/apache/geode/internal/i18n/StringIdResourceBundle_ja.txt'
-  exclude 'org/apache/geode/admin/doc-files/ds4_0.dtd'
+    exclude 'org/apache/geode/internal/i18n/StringIdResourceBundle_ja.txt'
+    exclude 'org/apache/geode/admin/doc-files/ds4_0.dtd'
 
-  inputs.property("moduleName", moduleName)
-  manifest {
-    attributes('Automatic-Module-Name': moduleName)
-  }
+    inputs.property("moduleName", moduleName)
+    manifest {
+        attributes('Automatic-Module-Name': moduleName)
+    }
 
 }
 
@@ -129,304 +129,304 @@ repeatUpgradeTest {
 }
 
 task raJar(type: Jar, dependsOn: classes) {
-  description 'Assembles the jar archive that contains the JCA classes'
-  from sourceSets.jca.output
-  exclude 'org/apache/geode/ra/**'
-  archiveName 'ra.jar'
+    description 'Assembles the jar archive that contains the JCA classes'
+    from sourceSets.jca.output
+    exclude 'org/apache/geode/ra/**'
+    archiveName 'ra.jar'
 }
 
 task jcaJar(type: Jar, dependsOn: raJar) {
-  description 'Assembles the jar archive that contains the JCA bundle'
-  baseName 'geode-jca'
-  extension 'rar'
-  metaInf { from 'src/jca/ra.xml' }
-  from raJar.archivePath
+    description 'Assembles the jar archive that contains the JCA bundle'
+    baseName 'geode-jca'
+    extension 'rar'
+    metaInf { from 'src/jca/ra.xml' }
+    from raJar.archivePath
 }
 
 configurations {
-  //declaring new configuration that will be used to associate with artifacts
-  archives
+    //declaring new configuration that will be used to associate with artifacts
+    archives
 
-  classesOutput {
-    extendsFrom api
-    description 'a dependency that exposes the compiled classes'
-  }
+    classesOutput {
+        extendsFrom api
+        description 'a dependency that exposes the compiled classes'
+    }
 
-  jmh {
-    extendsFrom testImplementation
-  }
+    jmh {
+        extendsFrom testImplementation
+    }
 }
 
 dependencies {
 
-  //These bom dependencies are used to constrain the versions of the dependencies listed below
-  api(platform(project(':boms:geode-all-bom')))
-  compileOnly(platform(project(':boms:geode-all-bom')))
-  testCompileOnly(platform(project(':boms:geode-all-bom')))
-  // As plugin configurations that do not extend from compile,
-  // we must explicitly impose version constraints on these configurations.
-  antlr platform(project(':boms:geode-all-bom'))
-  jcaAnnotationProcessor(platform(project(':boms:geode-all-bom')))
+    //These bom dependencies are used to constrain the versions of the dependencies listed below
+    api(platform(project(':boms:geode-all-bom')))
+    compileOnly(platform(project(':boms:geode-all-bom')))
+    testCompileOnly(platform(project(':boms:geode-all-bom')))
+    // As plugin configurations that do not extend from compile,
+    // we must explicitly impose version constraints on these configurations.
+    antlr platform(project(':boms:geode-all-bom'))
+    jcaAnnotationProcessor(platform(project(':boms:geode-all-bom')))
 
-  //A dependency that contains the compiled output of the source. What is this for?
-  classesOutput sourceSets.main.output
+    //A dependency that contains the compiled output of the source. What is this for?
+    classesOutput sourceSets.main.output
 
 
-  // Source Dependencies
-  //------------------------------------------------------------
+    // Source Dependencies
+    //------------------------------------------------------------
 
-  //  The antlr configuration is used by the antlr plugin, which compiles grammar
-  // files used by the query engine
-  antlr 'antlr:antlr'
+    //  The antlr configuration is used by the antlr plugin, which compiles grammar
+    // files used by the query engine
+    antlr 'antlr:antlr'
 
-  // External
-  //------------------------------------------------------------
+    // External
+    //------------------------------------------------------------
 
-  //Commons IO is used in persistence and management
-  api('commons-io:commons-io')
+    //Commons IO is used in persistence and management
+    api('commons-io:commons-io')
 
-  //tools.jar seems to be used by gfsh is some cases to control processes using
-  //the sun attach API? But this code path may not even be used?
-  compileOnly(files("${System.getProperty('java.home')}/../lib/tools.jar"))
+    //tools.jar seems to be used by gfsh is some cases to control processes using
+    //the sun attach API? But this code path may not even be used?
+    compileOnly(files("${System.getProperty('java.home')}/../lib/tools.jar"))
 
-  //Find bugs is used in multiple places in the code to suppress findbugs warnings
-  compileOnly('com.github.stephenc.findbugs:findbugs-annotations')
-  testCompileOnly('com.github.stephenc.findbugs:findbugs-annotations')
+    //Find bugs is used in multiple places in the code to suppress findbugs warnings
+    compileOnly('com.github.stephenc.findbugs:findbugs-annotations')
+    testCompileOnly('com.github.stephenc.findbugs:findbugs-annotations')
 
-  //Spring web is used for SerializableObjectHttpMessageConverter
-  implementation('org.springframework:spring-web')
-  // find bugs leaks in from spring, needed to remove warnings.
-  compileOnly('com.google.code.findbugs:jsr305')
+    //Spring web is used for SerializableObjectHttpMessageConverter
+    implementation('org.springframework:spring-web')
+    // find bugs leaks in from spring, needed to remove warnings.
+    compileOnly('com.google.code.findbugs:jsr305')
 
-  //Jgroups is a core component of our membership system.
-  implementation('org.jgroups:jgroups')
+    //Jgroups is a core component of our membership system.
+    implementation('org.jgroups:jgroups')
 
-  //Antlr is used by the query engine.
-  implementation('antlr:antlr')
+    //Antlr is used by the query engine.
+    implementation('antlr:antlr')
 
-  //Jackson annotations is used in gfsh
-  implementation('com.fasterxml.jackson.core:jackson-annotations')
+    //Jackson annotations is used in gfsh
+    implementation('com.fasterxml.jackson.core:jackson-annotations')
 
-  //Jackson databind is used in gfsh, and also in pdx
-  implementation('com.fasterxml.jackson.core:jackson-databind')
+    //Jackson databind is used in gfsh, and also in pdx
+    implementation('com.fasterxml.jackson.core:jackson-databind')
 
-  //Commons validator is used to validate inet addresses in membership
-  implementation('commons-validator:commons-validator')
+    //Commons validator is used to validate inet addresses in membership
+    implementation('commons-validator:commons-validator')
 
-  //javax.activation is runtime dependency for gfsh with java 11 (used by gfsh-over-http)
-  runtimeOnly('com.sun.activation:javax.activation')
+    //javax.activation is runtime dependency for gfsh with java 11 (used by gfsh-over-http)
+    runtimeOnly('com.sun.activation:javax.activation')
 
-  //jaxb is used by cluster configuration
-  implementation('javax.xml.bind:jaxb-api')
+    //jaxb is used by cluster configuration
+    implementation('javax.xml.bind:jaxb-api')
 
-  //jaxb is used by cluster configuration
-  implementation('com.sun.xml.bind:jaxb-impl')
+    //jaxb is used by cluster configuration
+    implementation('com.sun.xml.bind:jaxb-impl')
 
-  //istack appears to be used only by jaxb, not in our code. jaxb doesn't
-  //declare this as required dependency though. It's unclear if this is needed
-  //Runtime
-  runtimeOnly('com.sun.istack:istack-commons-runtime') {
-    exclude group: '*'
-  }
+    //istack appears to be used only by jaxb, not in our code. jaxb doesn't
+    //declare this as required dependency though. It's unclear if this is needed
+    //Runtime
+    runtimeOnly('com.sun.istack:istack-commons-runtime') {
+        exclude group: '*'
+    }
 
-  //Commons lang is used in many different places in core
-  implementation('org.apache.commons:commons-lang3')
+    //Commons lang is used in many different places in core
+    implementation('org.apache.commons:commons-lang3')
 
-  //Commons modeler is used by the (deprecated) admin API
-  implementation('commons-modeler:commons-modeler') {
-    exclude module: 'commons-logging-api'
-    exclude module: 'mx4j-jmx'
-    exclude module: 'xml-apis'
-    ext.optional = true
-  }
+    //Commons modeler is used by the (deprecated) admin API
+    implementation('commons-modeler:commons-modeler') {
+        exclude module: 'commons-logging-api'
+        exclude module: 'mx4j-jmx'
+        exclude module: 'xml-apis'
+        ext.optional = true
+    }
 
-  //micrometer is used for micrometer based metrics from geode geode
-  api('io.micrometer:micrometer-core')
+    //micrometer is used for micrometer based metrics from geode geode
+    api('io.micrometer:micrometer-core')
 
 
-  //FastUtil contains optimized collections that are used in multiple places in core
-  implementation('it.unimi.dsi:fastutil')
+    //FastUtil contains optimized collections that are used in multiple places in core
+    implementation('it.unimi.dsi:fastutil')
 
-  //Mail API is used by the deprecated admin API
-  implementation('javax.mail:javax.mail-api') {
-    ext.optional = true
-  }
+    //Mail API is used by the deprecated admin API
+    implementation('javax.mail:javax.mail-api') {
+        ext.optional = true
+    }
+
+    //The resource-API is used by the JCA support.
+    api('javax.resource:javax.resource-api')
+
+
+    //MX4J is used by the old admin API
+    implementation('mx4j:mx4j') {
+        ext.optional = true
+    }
+
+    //MX4J remote is used by the old admin API
+    implementation('mx4j:mx4j-remote') {
+        ext.optional = true
+    }
 
-  //The resource-API is used by the JCA support.
-  api('javax.resource:javax.resource-api')
-
-
-  //MX4J is used by the old admin API
-  implementation('mx4j:mx4j') {
-    ext.optional = true
-  }
-
-  //MX4J remote is used by the old admin API
-  implementation('mx4j:mx4j-remote') {
-    ext.optional = true
-  }
-
-  //MX4J tools is used by the old admin API
-  implementation('mx4j:mx4j-tools') {
-    ext.optional = true
-  }
-
-  //JNA is used for locking memory and preallocating disk files.
-  implementation('net.java.dev.jna:jna')
-  implementation('net.java.dev.jna:jna-platform')
-
-  //JOptSimple is used by gfsh. A couple of usages have leaked into DiskStore
-  implementation('net.sf.jopt-simple:jopt-simple')
-
-  //Log4j is used everywhere
-  implementation('org.apache.logging.log4j:log4j-api')
-
-
-  implementation('io.swagger:swagger-annotations')  {
-    ext.optional = true
-  }
-
-  runtimeOnly(project(':geode-http-service')) {
-    ext.optional = true
-  }
-
-  //Snappy is used for compressing values, if enabled
-  implementation('org.iq80.snappy:snappy') {
-    ext.optional = true
-  }
-
-  //Shiro is used for security checks throughout geode-core
-  //API - Shiro is exposed in geode's ResourcePermission class
-  api('org.apache.shiro:shiro-core')
-
-  //Classgraph is used by the gfsh cli, and also for function deployment (which happens in a server
-  //in response to a gfsh command)
-  implementation('io.github.classgraph:classgraph')
-
-  //RMIIO is used for uploading jar files and copying them between locator an servers
-  implementation('com.healthmarketscience.rmiio:rmiio')
-
-  //Geode-common has annotations and other pieces used geode-core
-  api(project(':geode-common'))
-  implementation(project(':geode-logging'))
-  implementation(project(':geode-membership'))
-  implementation(project(':geode-unsafe'))
-  implementation(project(':geode-serialization'))
-  implementation(project(':geode-tcp-server'))
-
-  //geode-management currently has pieces of the public API
-  //copied into it, so it is an API dependency
-  api(project(':geode-management'))
-
-
-  jcaCompile(sourceSets.main.output)
-
-  testImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  testImplementation(project(':geode-concurrency-test'))
-  testImplementation('org.apache.bcel:bcel')
-  testImplementation('org.assertj:assertj-core')
-  testImplementation('org.mockito:mockito-core')
-  testImplementation('com.pholser:junit-quickcheck-core')
-  testImplementation('org.powermock:powermock-core')
-  testImplementation('org.powermock:powermock-module-junit4')
-  testImplementation('org.powermock:powermock-api-mockito2')
-  testImplementation('pl.pragmatists:JUnitParams')
-  testImplementation('com.tngtech.archunit:archunit-junit4')
-
-  testImplementation(files("${System.getProperty('java.home')}/../lib/tools.jar"))
-
-  testRuntime('commons-collections:commons-collections')
-  testRuntime('commons-configuration:commons-configuration')
-  testRuntime('commons-io:commons-io')
-  testRuntime('commons-validator:commons-validator')
-  testRuntime('com.pholser:junit-quickcheck-generators')
-  testRuntime(project(path: ':geode-old-versions', configuration: 'testOutput'))
-
-  integrationTestImplementation(project(':geode-gfsh'))
-  integrationTestImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  integrationTestImplementation(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
-  integrationTestImplementation(project(':geode-log4j')) {
-    exclude module: 'geode-core'
-  }
-  integrationTestImplementation(project(':geode-concurrency-test'))
-  integrationTestImplementation('org.apache.bcel:bcel')
-  integrationTestImplementation('org.apache.logging.log4j:log4j-core')
-  integrationTestImplementation('org.powermock:powermock-core')
-  integrationTestImplementation('org.powermock:powermock-module-junit4')
-  integrationTestImplementation('org.powermock:powermock-api-mockito2')
-  integrationTestImplementation('pl.pragmatists:JUnitParams')
-  integrationTestImplementation('com.tngtech.archunit:archunit-junit4')
-
-
-  integrationTestRuntimeOnly('org.apache.derby:derby')
-  integrationTestRuntimeOnly('xerces:xercesImpl')
-
-  distributedTestImplementation(project(':geode-gfsh'))
-  distributedTestImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  distributedTestImplementation(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
-  distributedTestImplementation(project(':geode-log4j')) {
-    exclude module: 'geode-core'
-  }
-  distributedTestImplementation('pl.pragmatists:JUnitParams')
-  distributedTestImplementation('com.jayway.jsonpath:json-path-assert')
-  distributedTestImplementation('net.openhft:compiler')
-
-  distributedTestRuntimeOnly(project(path: ':geode-old-versions', configuration: 'testOutput'))
-  distributedTestRuntimeOnly('org.apache.derby:derby')
-
-
-  upgradeTestImplementation(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
-
-  upgradeTestRuntimeOnly(project(path: ':geode-old-versions', configuration: 'testOutput'))
-  upgradeTestRuntimeOnly(project(':geode-log4j'))
-
-
-  performanceTestImplementation(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  performanceTestImplementation(project(':geode-log4j'))
+    //MX4J tools is used by the old admin API
+    implementation('mx4j:mx4j-tools') {
+        ext.optional = true
+    }
+
+    //JNA is used for locking memory and preallocating disk files.
+    implementation('net.java.dev.jna:jna')
+    implementation('net.java.dev.jna:jna-platform')
+
+    //JOptSimple is used by gfsh. A couple of usages have leaked into DiskStore
+    implementation('net.sf.jopt-simple:jopt-simple')
+
+    //Log4j is used everywhere
+    implementation('org.apache.logging.log4j:log4j-api')
+
+
+    implementation('io.swagger:swagger-annotations') {
+        ext.optional = true
+    }
+
+    runtimeOnly(project(':geode-http-service')) {
+        ext.optional = true
+    }
+
+    //Snappy is used for compressing values, if enabled
+    implementation('org.iq80.snappy:snappy') {
+        ext.optional = true
+    }
+
+    //Shiro is used for security checks throughout geode-core
+    //API - Shiro is exposed in geode's ResourcePermission class
+    api('org.apache.shiro:shiro-core')
+
+    //Classgraph is used by the gfsh cli, and also for function deployment (which happens in a server
+    //in response to a gfsh command)
+    implementation('io.github.classgraph:classgraph')
+
+    //RMIIO is used for uploading jar files and copying them between locator an servers
+    implementation('com.healthmarketscience.rmiio:rmiio')
+
+    //Geode-common has annotations and other pieces used geode-core
+    api(project(':geode-common'))
+    implementation(project(':geode-logging'))
+    implementation(project(':geode-membership'))
+    implementation(project(':geode-unsafe'))
+    implementation(project(':geode-serialization'))
+    implementation(project(':geode-tcp-server'))
+
+    //geode-management currently has pieces of the public API
+    //copied into it, so it is an API dependency
+    api(project(':geode-management'))
+
+
+    jcaCompile(sourceSets.main.output)
+
+    testImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    testImplementation(project(':geode-concurrency-test'))
+    testImplementation('org.apache.bcel:bcel')
+    testImplementation('org.assertj:assertj-core')
+    testImplementation('org.mockito:mockito-core')
+    testImplementation('com.pholser:junit-quickcheck-core')
+    testImplementation('org.powermock:powermock-core')
+    testImplementation('org.powermock:powermock-module-junit4')
+    testImplementation('org.powermock:powermock-api-mockito2')
+    testImplementation('pl.pragmatists:JUnitParams')
+    testImplementation('com.tngtech.archunit:archunit-junit4')
+
+    testImplementation(files("${System.getProperty('java.home')}/../lib/tools.jar"))
+
+    testRuntime('commons-collections:commons-collections')
+    testRuntime('commons-configuration:commons-configuration')
+    testRuntime('commons-io:commons-io')
+    testRuntime('commons-validator:commons-validator')
+    testRuntime('com.pholser:junit-quickcheck-generators')
+    testRuntime(project(path: ':geode-old-versions', configuration: 'testOutput'))
+
+    integrationTestImplementation(project(':geode-gfsh'))
+    integrationTestImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    integrationTestImplementation(project(':geode-dunit')) {
+        exclude module: 'geode-core'
+    }
+    integrationTestImplementation(project(':geode-log4j')) {
+        exclude module: 'geode-core'
+    }
+    integrationTestImplementation(project(':geode-concurrency-test'))
+    integrationTestImplementation('org.apache.bcel:bcel')
+    integrationTestImplementation('org.apache.logging.log4j:log4j-core')
+    integrationTestImplementation('org.powermock:powermock-core')
+    integrationTestImplementation('org.powermock:powermock-module-junit4')
+    integrationTestImplementation('org.powermock:powermock-api-mockito2')
+    integrationTestImplementation('pl.pragmatists:JUnitParams')
+    integrationTestImplementation('com.tngtech.archunit:archunit-junit4')
+
+
+    integrationTestRuntimeOnly('org.apache.derby:derby')
+    integrationTestRuntimeOnly('xerces:xercesImpl')
+
+    distributedTestImplementation(project(':geode-gfsh'))
+    distributedTestImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    distributedTestImplementation(project(':geode-dunit')) {
+        exclude module: 'geode-core'
+    }
+    distributedTestImplementation(project(':geode-log4j')) {
+        exclude module: 'geode-core'
+    }
+    distributedTestImplementation('pl.pragmatists:JUnitParams')
+    distributedTestImplementation('com.jayway.jsonpath:json-path-assert')
+    distributedTestImplementation('net.openhft:compiler')
+
+    distributedTestRuntimeOnly(project(path: ':geode-old-versions', configuration: 'testOutput'))
+    distributedTestRuntimeOnly('org.apache.derby:derby')
+
+
+    upgradeTestImplementation(project(':geode-dunit')) {
+        exclude module: 'geode-core'
+    }
+
+    upgradeTestRuntimeOnly(project(path: ':geode-old-versions', configuration: 'testOutput'))
+    upgradeTestRuntimeOnly(project(':geode-log4j'))
+
+
+    performanceTestImplementation(project(':geode-junit')) {
+        exclude module: 'geode-core'
+    }
+    performanceTestImplementation(project(':geode-log4j'))
 }
 
 jmh {
-  duplicateClassesStrategy = 'warn'
-  include = project.hasProperty('jmh.include') ? project.getProperties().get('jmh.include') : '.*'
-  profilers = project.hasProperty('jmh.profilers') ? project.getProperties().get('jmh.profilers').tokenize() : []
-  threads = project.hasProperty('jmh.threads') ? project.getProperties().get('jmh.threads') : null
+    duplicateClassesStrategy = 'warn'
+    include = project.hasProperty('jmh.include') ? project.getProperties().get('jmh.include') : '.*'
+    profilers = project.hasProperty('jmh.profilers') ? project.getProperties().get('jmh.profilers').tokenize() : []
+    threads = project.hasProperty('jmh.threads') ? project.getProperties().get('jmh.threads') : null
 }
 
 jmhJar {
-  inputs.files(project.sourceSets.main.output, project.sourceSets.test.output)
-  from {
-    [configurations.jmh, configurations.jmhRuntime, configurations.runtimeClasspath].collect {
-        it.asFileTree.collect {
-          it
+    inputs.files(project.sourceSets.main.output, project.sourceSets.test.output)
+    from {
+        [configurations.jmh, configurations.jmhRuntime, configurations.runtimeClasspath].collect {
+            it.asFileTree.collect {
+                it
+            }
+        }.flatten().unique().collect {
+            it.isDirectory() ? it : zipTree(it)
         }
-      }.flatten().unique().collect {
-          it.isDirectory() ? it : zipTree(it)
-      }
-  }
-  exclude(['META-INF/*.SF'])
-  exclude('META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat')
-  exclude('*.jar')
+    }
+    exclude(['META-INF/*.SF'])
+    exclude('META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat')
+    exclude('*.jar')
 }
 
 tasks.eclipse.dependsOn(generateGrammarSource)
 
 distributedTest {
-  // Some tests have inner tests that should be ignored
-  exclude "**/*\$*.class"
+    // Some tests have inner tests that should be ignored
+    exclude "**/*\$*.class"
 }
 
 rootProject.generate.dependsOn(generateGrammarSource)