This is an automated email from the ASF dual-hosted git repository. bross pushed a commit to branch support/1.12 in repository https://gitbox.apache.org/repos/asf/geode.git
commit 64687d7c7d02e4dc082e0b2e1c312fb8ee26850a Author: Jacob Barrett <jbarr...@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. (cherry picked from commit 9939cc0f2f1caad051bd104a0a06a4e1737d3830) --- ...AbstractCommitSessionValveIntegrationTest.java} | 42 +++++----- .../AbstractSessionValveIntegrationTest.java | 0 .../CommitSessionValveIntegrationTest.java | 57 +++++++++++++ .../catalina/Tomcat7CommitSessionOutputBuffer.java | 53 ++++++++++++ .../catalina/Tomcat7CommitSessionValve.java | 58 +++++++++++++ .../catalina/Tomcat7DeltaSessionManager.java | 12 ++- .../Tomcat7CommitSessionOutputBufferTest.java | 63 ++++++++++++++ .../catalina/Tomcat7CommitSessionValveTest.java | 98 ++++++++++++++++++++++ .../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 ++++++++++++++++++++++ .../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 | 82 ++++++++++++++++++ .../session/catalina/CommitSessionValve.java | 69 --------------- .../session/catalina/DeltaSessionManager.java | 9 +- .../session/catalina/SessionCommitter.java} | 26 ++---- .../catalina/Tomcat6CommitSessionValve.java} | 26 ++---- .../catalina/Tomcat6DeltaSessionManager.java | 7 +- .../integrationTest/resources/assembly_content.txt | 6 +- 28 files changed, 1153 insertions(+), 139 deletions(-) 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/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/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/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..6276086 --- /dev/null +++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractCommitSessionValve.java @@ -0,0 +1,82 @@ +/* + * 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.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) { + @SuppressWarnings("unchecked") + final DeltaSessionManager<SelfT> deltaSessionManager = (DeltaSessionManager<SelfT>) 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 2af36fe..86103df 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 @@ -63,7 +63,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"; @@ -91,7 +92,7 @@ public abstract class DeltaSessionManager extends ManagerBase private Valve jvmRouteBinderValve; - private Valve commitSessionValve; + private CommitSessionValveT commitSessionValve; private SessionCache sessionCache; @@ -601,10 +602,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/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt index fca61e9..1eaf681 100644 --- a/geode-assembly/src/integrationTest/resources/assembly_content.txt +++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt @@ -824,7 +824,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