diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java new file mode 100644 index 000000000000..349a9c2d515b --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/AbstractSessionValveIntegrationTest.java @@ -0,0 +1,111 @@ +/* + * 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.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.http.HttpSession; + +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.junit.Rule; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +public class AbstractSessionValveIntegrationTest { + private static final String REGION_NAME = "geode_modules_sessions"; + static final String TEST_CONTEXT = "testContext"; + static final String TEST_JVM_ROUTE = "testJvmRoute"; + static final String TEST_SESSION_ID = UUID.randomUUID().toString() + "." + TEST_JVM_ROUTE; + + TestDeltaSession deltaSession; + DeltaSessionManager deltaSessionManager; + + Region httpSessionRegion; + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + private void mockDeltaSessionManager() { + deltaSessionManager = mock(DeltaSessionManager.class); + + when(deltaSessionManager.getLogger()).thenReturn(mock(Log.class)); + SessionCache mockSessionCache = mock(AbstractSessionCache.class); + doCallRealMethod().when(mockSessionCache).getSession(any()); + when(mockSessionCache.getOperatingRegion()).thenReturn(httpSessionRegion); + when(deltaSessionManager.getSessionCache()).thenReturn(mockSessionCache); + } + + protected void parameterizedSetUp(RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .create(REGION_NAME); + + mockDeltaSessionManager(); + deltaSession = spy(new TestDeltaSession(deltaSessionManager, TEST_SESSION_ID)); + + httpSessionRegion.put(deltaSession.getId(), deltaSession); + } + + static class TestDeltaSession extends DeltaSession { + + TestDeltaSession(Manager manager, String sessionId) { + super(manager); + this.id = sessionId; + } + + @Override + public String getContextName() { + return TEST_CONTEXT; + } + + @Override + protected boolean isValidInternal() { + return true; + } + } + + static class TestValve extends ValveBase { + final boolean throwException; + final AtomicInteger invocations = new AtomicInteger(0); + final AtomicInteger exceptionsThrown = new AtomicInteger(0); + + TestValve(boolean throwException) { + this.throwException = throwException; + } + + @Override + public void invoke(Request request, Response response) { + invocations.incrementAndGet(); + + if (throwException) { + exceptionsThrown.incrementAndGet(); + throw new RuntimeException("Mock Exception"); + } + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java new file mode 100644 index 000000000000..c7e056eac783 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/CommitSessionValveIntegrationTest.java @@ -0,0 +1,147 @@ +/* + * 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.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; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +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.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); + testValve = new TestValve(false); + + commitSessionValve = new CommitSessionValve(); + commitSessionValve.setNext(testValve); + } + + protected void parameterizedSetUp(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)); + when(deltaSessionManager.getTheContext().getLogger()).thenReturn(mock(Log.class)); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerDoesNotBelongToGeode( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(request.getContext().getManager()).thenReturn(mock(Manager.class)); + + commitSessionValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).getSession(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerBelongsToGeodeButSessionDoesNotExist( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + doReturn(null).when(request).getSession(false); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + commitSessionValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(1)).getSession(false); + verify(deltaSessionManager, times(0)).removeTouchedSession(anyString()); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerBelongsToGeodeAndSessionExistsButIsNotValid( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + doReturn(false).when(deltaSession).isValid(); + doReturn(deltaSessionFacade).when(request).getSession(false); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + commitSessionValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(1)).getSession(false); + verify(deltaSessionManager, times(0)).removeTouchedSession(anyString()); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndCommitTheExistingValidSessionWhenSessionManagerBelongsToGeode( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + deltaSessionManager.addSessionToTouch(TEST_SESSION_ID); + doReturn(deltaSessionFacade).when(request).getSession(false); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + commitSessionValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + assertThat(httpSessionRegion.containsKey(TEST_SESSION_ID)).isTrue(); + assertThat(deltaSessionManager.getSessionsToTouch().contains(TEST_SESSION_ID)).isFalse(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCommitTheExistingValidSessionWhenSessionManagerBelongsToGeodeEvenWhenTheNextChainedValveThrowsAnException( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + TestValve exceptionValve = new TestValve(true); + commitSessionValve.setNext(exceptionValve); + deltaSessionManager.addSessionToTouch(TEST_SESSION_ID); + doReturn(deltaSessionFacade).when(request).getSession(false); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + assertThatThrownBy(() -> commitSessionValve.invoke(request, response)) + .isInstanceOf(RuntimeException.class); + assertThat(exceptionValve.invocations.get()).isEqualTo(1); + assertThat(exceptionValve.exceptionsThrown.get()).isEqualTo(1); + assertThat(httpSessionRegion.containsKey(TEST_SESSION_ID)).isTrue(); + assertThat(deltaSessionManager.getSessionsToTouch().contains(TEST_SESSION_ID)).isFalse(); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/JvmRouteBinderValveIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/JvmRouteBinderValveIntegrationTest.java new file mode 100644 index 000000000000..d7b8c1a2fa99 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/JvmRouteBinderValveIntegrationTest.java @@ -0,0 +1,175 @@ +/* + * 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.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +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; + +import java.io.IOException; +import java.util.UUID; + +import javax.servlet.ServletException; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.apache.catalina.Context; +import org.apache.catalina.Manager; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.RegionShortcut; + +@RunWith(JUnitParamsRunner.class) +public class JvmRouteBinderValveIntegrationTest extends AbstractSessionValveIntegrationTest { + private Request request; + private Response response; + private TestValve testValve; + private JvmRouteBinderValve jvmRouteBinderValve; + + @Before + public void setUp() { + request = spy(Request.class); + response = spy(Response.class); + testValve = new TestValve(false); + + jvmRouteBinderValve = new JvmRouteBinderValve(); + jvmRouteBinderValve.setNext(testValve); + } + + protected void parameterizedSetUp(RegionShortcut regionShortcut) { + super.parameterizedSetUp(regionShortcut); + when(request.getContext()).thenReturn(mock(Context.class)); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerDoesNotBelongToGeode( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(request.getContext().getManager()).thenReturn(mock(Manager.class)); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(deltaSessionManager, times(0)).getJvmRoute(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCallNextChainedValveAndDoNothingWhenSessionManagerBelongsToGeodeButJvmRouteIsNull( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + doReturn(null).when(deltaSessionManager).getJvmRoute(); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).getRequestedSessionId(); + verify(deltaSessionManager, times(1)).getJvmRoute(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldNotFailoverWhenRequestedSessionIdIsNull(RegionShortcut regionShortcut) + throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(deltaSessionManager.getJvmRoute()).thenReturn(""); + when(request.getRequestedSessionId()).thenReturn(null); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).changeSessionId(anyString()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getId()).isEqualTo(TEST_SESSION_ID); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldNotFailoverWhenJvmRouteWithinTheRequestedSessionIdIsNull( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(deltaSessionManager.getJvmRoute()).thenReturn(""); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + when(request.getRequestedSessionId()).thenReturn(UUID.randomUUID().toString()); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).changeSessionId(anyString()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getId()).isEqualTo(TEST_SESSION_ID); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldNotFailoverWhenJvmRouteWithinTheRequestedSessionIsSameAsLocalJvmRoute( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(deltaSessionManager.getJvmRoute()).thenReturn(TEST_JVM_ROUTE); + when(request.getRequestedSessionId()).thenReturn(TEST_SESSION_ID); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).changeSessionId(anyString()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getId()).isEqualTo(TEST_SESSION_ID); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldNotFailoverWhenJvmRoutesDifferAndManagerCanNotFindOriginalSession( + RegionShortcut regionShortcut) throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(deltaSessionManager.getJvmRoute()).thenReturn("jvmRoute"); + doCallRealMethod().when(deltaSessionManager).findSession(anyString()); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + when(request.getRequestedSessionId()).thenReturn("nonExistingSessionId.anotherJvmRoute"); + + jvmRouteBinderValve.invoke(request, response); + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(0)).changeSessionId(anyString()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getId()).isEqualTo(TEST_SESSION_ID); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void invokeShouldCorrectlyHandleSessionFailover(RegionShortcut regionShortcut) + throws IOException, ServletException { + parameterizedSetUp(regionShortcut); + when(deltaSessionManager.getJvmRoute()).thenReturn("jvmRoute"); + when(deltaSessionManager.getContextName()).thenReturn(TEST_CONTEXT); + when(deltaSessionManager.getContainer()).thenReturn(mock(Context.class)); + when(((Context) deltaSessionManager.getContainer()).getApplicationLifecycleListeners()) + .thenReturn(new Object[] {}); + doCallRealMethod().when(deltaSessionManager).findSession(anyString()); + + when(request.getRequestedSessionId()).thenReturn(TEST_SESSION_ID); + when(request.getContext().getManager()).thenReturn(deltaSessionManager); + + jvmRouteBinderValve.invoke(request, response); + String expectedFailoverSessionId = + TEST_SESSION_ID.substring(0, TEST_SESSION_ID.indexOf(".") + 1) + "jvmRoute"; + assertThat(testValve.invocations.get()).isEqualTo(1); + verify(request, times(1)).changeSessionId(expectedFailoverSessionId); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getId()).isEqualTo(expectedFailoverSessionId); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheLoaderIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheLoaderIntegrationTest.java new file mode 100644 index 000000000000..f11d8386f14a --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheLoaderIntegrationTest.java @@ -0,0 +1,119 @@ +/* + * 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.callback; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.Enumeration; + +import javax.servlet.http.HttpSession; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.DeltaSession; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +@RunWith(JUnitParamsRunner.class) +public class LocalSessionCacheLoaderIntegrationTest { + private static final String SESSION_ID = "id"; + private static final String REGION_NAME = "sessions"; + private static final String BACKING_REGION_NAME = "sessions_local"; + private HttpSession mockSession; + private Region localHttpSessionRegion; + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + @Before + public void setUp() { + mockSession = new TestDeltaSession(SESSION_ID, "mockSession"); + } + + public void parameterizedSetUp(RegionShortcut regionShortcut) { + Region backingHttpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .create(REGION_NAME); + backingHttpSessionRegion.put(SESSION_ID, mockSession); + + localHttpSessionRegion = server.getCache() + .createRegionFactory(RegionShortcut.LOCAL_HEAP_LRU) + .setCacheLoader(new LocalSessionCacheLoader(backingHttpSessionRegion)) + .create(BACKING_REGION_NAME); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void getShouldReturnEntryFromTheBackingRegion(RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + + HttpSession session = localHttpSessionRegion.get(SESSION_ID); + assertThat(session).isEqualTo(mockSession); + } + + static class TestDeltaSession extends DeltaSession { + private final String id; + private final String value; + + TestDeltaSession(String id, String value) { + this.id = id; + this.value = value; + } + + public String getId() { + return id; + } + + String getValue() { + return value; + } + + @Override + public Enumeration getAttributeNames() { + return Collections.emptyEnumeration(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestDeltaSession that = (TestDeltaSession) o; + + if (!getId().equals(that.getId())) { + return false; + } + return getValue().equals(that.getValue()); + } + + @Override + public int hashCode() { + int result = getId().hashCode(); + result = 31 * result + getValue().hashCode(); + return result; + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheWriterIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheWriterIntegrationTest.java new file mode 100644 index 000000000000..866417b8b6d0 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/LocalSessionCacheWriterIntegrationTest.java @@ -0,0 +1,238 @@ +/* + * 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.callback; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.http.HttpSession; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.CacheWriterException; +import org.apache.geode.cache.EntryEvent; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.util.CacheWriterAdapter; +import org.apache.geode.modules.session.catalina.DeltaSession; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +@RunWith(JUnitParamsRunner.class) +public class LocalSessionCacheWriterIntegrationTest { + private static final String SESSION_ID = "id"; + private static final String REGION_NAME = "sessions"; + private static final String BACKING_REGION_NAME = "sessions_local"; + private HttpSession mockSession; + private HttpSession mockUpdateSession; + private Region localHttpSessionRegion; + private Region backingHttpSessionRegion; + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + @Before + public void setUp() { + mockSession = new TestDeltaSession(SESSION_ID, "mockSession"); + mockUpdateSession = new TestDeltaSession(SESSION_ID, "mockUpdateSession"); + + ExceptionThrower.throwExceptionOnCreate.set(false); + ExceptionThrower.throwExceptionOnUpdate.set(false); + ExceptionThrower.throwExceptionOnDestroy.set(false); + } + + public void parameterizedSetUp(RegionShortcut regionShortcut) { + backingHttpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .setCacheWriter(new ExceptionThrower()) + .create(REGION_NAME); + + localHttpSessionRegion = server.getCache() + .createRegionFactory(RegionShortcut.LOCAL_HEAP_LRU) + .setCacheWriter(new LocalSessionCacheWriter(backingHttpSessionRegion)) + .create(BACKING_REGION_NAME); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void createAndUpdateShouldBeAppliedToLocalRegionWhenTheySucceededOnTheBackingRegion( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + + // First time - Create + localHttpSessionRegion.put(SESSION_ID, mockSession); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(backingHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockSession); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockSession); + + // Second time - Update + localHttpSessionRegion.put(SESSION_ID, mockUpdateSession); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(backingHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockUpdateSession); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockUpdateSession); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void createAndUpdateShouldNotBeAppliedToLocalRegionWhenTheyFailedOnTheBackingRegion( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + + // First time - Create + ExceptionThrower.throwExceptionOnCreate.set(true); + assertThatThrownBy(() -> localHttpSessionRegion.put(SESSION_ID, mockSession)) + .isInstanceOf(RuntimeException.class).hasMessage("Mock Exception"); + assertThat(backingHttpSessionRegion.isEmpty()).isTrue(); + assertThat(localHttpSessionRegion.isEmpty()).isTrue(); + + // Allow Create and Try to Update + ExceptionThrower.throwExceptionOnCreate.set(false); + localHttpSessionRegion.put(SESSION_ID, mockSession); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + ExceptionThrower.throwExceptionOnUpdate.set(true); + + assertThatThrownBy(() -> localHttpSessionRegion.put(SESSION_ID, mockUpdateSession)) + .isInstanceOf(RuntimeException.class).hasMessage("Mock Exception"); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(backingHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockSession); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.get(SESSION_ID)).isEqualTo(mockSession); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void destroyShouldBeAppliedToLocalRegionWhenItSucceededOnTheBackingRegion( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + localHttpSessionRegion.put(SESSION_ID, mockSession); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + + localHttpSessionRegion.destroy(SESSION_ID); + assertThat(backingHttpSessionRegion.isEmpty()).isTrue(); + assertThat(localHttpSessionRegion.isEmpty()).isTrue(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void destroyShouldBeAppliedToLocalRegionEvenWhenItFailedOnTheBackingRegionWithEntryNotFoundException( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + localHttpSessionRegion.put(SESSION_ID, mockSession); + backingHttpSessionRegion.destroy(SESSION_ID); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + + localHttpSessionRegion.destroy(SESSION_ID); + assertThat(backingHttpSessionRegion.isEmpty()).isTrue(); + assertThat(localHttpSessionRegion.isEmpty()).isTrue(); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void destroyShouldNotBeAppliedToLocalRegionWhenItFailedOnTheBackingRegion( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + localHttpSessionRegion.put(SESSION_ID, mockSession); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + + ExceptionThrower.throwExceptionOnDestroy.set(true); + assertThatThrownBy(() -> localHttpSessionRegion.destroy(SESSION_ID)) + .isInstanceOf(RuntimeException.class).hasMessage("Mock Exception"); + assertThat(backingHttpSessionRegion.size()).isEqualTo(1); + assertThat(localHttpSessionRegion.size()).isEqualTo(1); + } + + static class ExceptionThrower extends CacheWriterAdapter { + static AtomicBoolean throwExceptionOnCreate = new AtomicBoolean(false); + static AtomicBoolean throwExceptionOnUpdate = new AtomicBoolean(false); + static AtomicBoolean throwExceptionOnDestroy = new AtomicBoolean(false); + + @Override + public void beforeCreate(EntryEvent event) throws CacheWriterException { + if (throwExceptionOnCreate.get()) + throw new RuntimeException("Mock Exception"); + } + + @Override + public void beforeUpdate(EntryEvent event) throws CacheWriterException { + if (throwExceptionOnUpdate.get()) + throw new RuntimeException("Mock Exception"); + } + + @Override + public void beforeDestroy(EntryEvent event) throws CacheWriterException { + if (throwExceptionOnDestroy.get()) + throw new RuntimeException("Mock Exception"); + } + } + + static class TestDeltaSession extends DeltaSession { + private final String id; + private final String value; + + TestDeltaSession(String id, String value) { + this.id = id; + this.value = value; + } + + public String getId() { + return id; + } + + String getValue() { + return value; + } + + @Override + public Enumeration getAttributeNames() { + return Collections.emptyEnumeration(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestDeltaSession that = (TestDeltaSession) o; + + if (!getId().equals(that.getId())) { + return false; + } + return getValue().equals(that.getValue()); + } + + @Override + public int hashCode() { + int result = getId().hashCode(); + result = 31 * result + getValue().hashCode(); + return result; + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerIntegrationTest.java new file mode 100644 index 000000000000..c7c47524ffd8 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerIntegrationTest.java @@ -0,0 +1,128 @@ +/* + * 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.callback; + +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.http.HttpSession; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.apache.juli.logging.Log; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.CustomExpiry; +import org.apache.geode.cache.ExpirationAction; +import org.apache.geode.cache.ExpirationAttributes; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.DeltaSession; +import org.apache.geode.modules.session.catalina.DeltaSessionManager; +import org.apache.geode.modules.session.catalina.SessionCache; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +@RunWith(JUnitParamsRunner.class) +public class SessionExpirationCacheListenerIntegrationTest { + private static final String KEY = "key1"; + private static final String REGION_NAME = "sessions"; + private TestDeltaSession deltaSession; + private Region httpSessionRegion; + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + @Before + public void setUp() { + deltaSession = new TestDeltaSession(KEY); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void listenerShouldInvokeProcessExpiredMethodWhenSessionIsDestroyedDueToGeodeExpiration( + RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .setCustomEntryTimeToLive(new TestCustomExpiry()) + .addCacheListener(new SessionExpirationCacheListener()) + .create(REGION_NAME); + httpSessionRegion.put(deltaSession.getId(), deltaSession); + + await().untilAsserted(() -> assertThat(httpSessionRegion.isEmpty()).isTrue()); + assertThat(deltaSession.processExpiredCalls.get()).isEqualTo(1); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void listenerShouldInvokeProcessExpiredMethodWhenSessionIsDestroyedDueToGeodeExpirationWithoutClientProxy( + RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .addCacheListener(new SessionExpirationCacheListener()) + .create(REGION_NAME); + + DeltaSessionManager manager = mock(DeltaSessionManager.class); + when(manager.getLogger()).thenReturn(mock(Log.class)); + when(manager.getRegionName()).thenReturn(REGION_NAME); + when(manager.getSessionCache()).thenReturn(mock(SessionCache.class)); + when(manager.getSessionCache().getOperatingRegion()).thenReturn(httpSessionRegion); + deltaSession.setOwner(manager); + httpSessionRegion.put(deltaSession.getId(), deltaSession); + + deltaSession.expire(true); + await().untilAsserted(() -> assertThat(httpSessionRegion.isEmpty()).isTrue()); + assertThat(deltaSession.processExpiredCalls.get()).isEqualTo(1); + } + + static class TestCustomExpiry implements CustomExpiry { + @Override + public ExpirationAttributes getExpiry(Region.Entry entry) { + return new ExpirationAttributes(1, ExpirationAction.DESTROY); + } + } + + static class TestDeltaSession extends DeltaSession { + private final String id; + final AtomicInteger processExpiredCalls = new AtomicInteger(0); + + TestDeltaSession(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + @Override + public void processExpired() { + processExpiredCalls.incrementAndGet(); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.emptyEnumeration(); + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/AbstractDeltaSessionIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/AbstractDeltaSessionIntegrationTest.java new file mode 100644 index 000000000000..97b97802a50d --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/AbstractDeltaSessionIntegrationTest.java @@ -0,0 +1,118 @@ +/* + * 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.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.UUID; + +import javax.servlet.http.HttpSession; + +import org.apache.catalina.Context; +import org.apache.catalina.Manager; +import org.apache.juli.logging.Log; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.DataSerializable; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.DeltaSession; +import org.apache.geode.modules.session.catalina.DeltaSessionManager; +import org.apache.geode.modules.session.catalina.SessionCache; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +public abstract class AbstractDeltaSessionIntegrationTest { + static final String REGION_NAME = "geode_modules_sessions"; + static final String TEST_SESSION_ID = UUID.randomUUID().toString(); + static final String FIRST_ATTRIBUTE_KEY = "FIRST_ATTRIBUTE_KEY"; + static final String SECOND_ATTRIBUTE_KEY = "SECOND_ATTRIBUTE_KEY"; + static final String FIRST_ATTRIBUTE_VALUE = "FIRST_ATTRIBUTE_VALUE"; + static final String SECOND_ATTRIBUTE_VALUE = "SECOND_ATTRIBUTE_VALUE"; + + DeltaSessionManager deltaSessionManager; + + Region httpSessionRegion; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + void mockDeltaSessionManager() { + deltaSessionManager = mock(DeltaSessionManager.class); + + when(deltaSessionManager.getLogger()).thenReturn(mock(Log.class)); + when(deltaSessionManager.getRegionName()).thenReturn(REGION_NAME); + when(deltaSessionManager.isBackingCacheAvailable()).thenReturn(true); + when(deltaSessionManager.getContainer()).thenReturn(mock(Context.class)); + when(deltaSessionManager.getSessionCache()).thenReturn(mock(SessionCache.class)); + when(deltaSessionManager.getSessionCache().getOperatingRegion()).thenReturn(httpSessionRegion); + } + + void parameterizedSetUp(RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .create(REGION_NAME); + + mockDeltaSessionManager(); + TestDeltaSession deltaSession = new TestDeltaSession(deltaSessionManager, TEST_SESSION_ID); + deltaSession.setAttribute(FIRST_ATTRIBUTE_KEY, FIRST_ATTRIBUTE_VALUE); + deltaSession.setAttribute(SECOND_ATTRIBUTE_KEY, SECOND_ATTRIBUTE_VALUE); + + httpSessionRegion.put(deltaSession.getId(), deltaSession); + } + + DataSerializable serializeDeserializeObject(DataSerializable originalObject) + throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException { + File tempFile = temporaryFolder.newFile(); + OutputStream outputStream = new FileOutputStream(tempFile); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); + originalObject.toData(objectOutputStream); + objectOutputStream.close(); + outputStream.close(); + + DataSerializable deserializeObject = originalObject.getClass().newInstance(); + FileInputStream inputStream = new FileInputStream(tempFile); + ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); + deserializeObject.fromData(objectInputStream); + objectInputStream.close(); + inputStream.close(); + + return deserializeObject; + } + + static class TestDeltaSession extends DeltaSession { + + TestDeltaSession(Manager manager, String sessionId) { + super(manager); + this.id = sessionId; + } + + @Override + protected boolean isValidInternal() { + return true; + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchIntegrationTest.java new file mode 100644 index 000000000000..b052a1487bf6 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchIntegrationTest.java @@ -0,0 +1,84 @@ +/* + * 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.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.RegionShortcut; + +@RunWith(JUnitParamsRunner.class) +public class DeltaSessionAttributeEventBatchIntegrationTest + extends AbstractDeltaSessionIntegrationTest { + + @Test + public void toDataAndFromDataShouldWorkProperly() + throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException { + List queue = new ArrayList<>(); + queue.add(new DeltaSessionDestroyAttributeEvent(SECOND_ATTRIBUTE_KEY)); + queue.add(new DeltaSessionUpdateAttributeEvent(FIRST_ATTRIBUTE_KEY, FIRST_ATTRIBUTE_VALUE)); + DeltaSessionAttributeEventBatch originalBatch = + new DeltaSessionAttributeEventBatch(REGION_NAME, TEST_SESSION_ID, queue); + DeltaSessionAttributeEventBatch deserializeBatch = + (DeltaSessionAttributeEventBatch) serializeDeserializeObject(originalBatch); + + assertThat(deserializeBatch.getKey()).isEqualTo(originalBatch.getKey()); + assertThat(originalBatch.getEventQueue()).isEqualTo(originalBatch.getEventQueue()); + assertThat(deserializeBatch.getRegionName()).isEqualTo(originalBatch.getRegionName()); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void applyBatchShouldNotDoAnythingIfSessionIdDoesNotExist(RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + DeltaSessionAttributeEventBatch deltaSessionAttributeEventBatch = + new DeltaSessionAttributeEventBatch(REGION_NAME, "fakeSessionId", + Collections.singletonList(new DeltaSessionDestroyAttributeEvent(FIRST_ATTRIBUTE_KEY))); + + // Apply batch and verify local session entry is not modified. + deltaSessionAttributeEventBatch.apply(server.getCache()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getAttribute(FIRST_ATTRIBUTE_KEY)) + .isEqualTo(FIRST_ATTRIBUTE_VALUE); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getAttribute(SECOND_ATTRIBUTE_KEY)) + .isEqualTo(SECOND_ATTRIBUTE_VALUE); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void applyBatchShouldApplyRemoteEventsOnLocalSession(RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + List queue = new ArrayList<>(); + queue.add(new DeltaSessionDestroyAttributeEvent(SECOND_ATTRIBUTE_KEY)); + queue.add(new DeltaSessionUpdateAttributeEvent(FIRST_ATTRIBUTE_KEY, SECOND_ATTRIBUTE_VALUE)); + DeltaSessionAttributeEventBatch deltaSessionAttributeEventBatch = + new DeltaSessionAttributeEventBatch(REGION_NAME, TEST_SESSION_ID, queue); + + // Apply batch and verify local session entry is modified. + deltaSessionAttributeEventBatch.apply(server.getCache()); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getAttribute(SECOND_ATTRIBUTE_KEY)) + .isNull(); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getAttribute(FIRST_ATTRIBUTE_KEY)) + .isEqualTo(SECOND_ATTRIBUTE_VALUE); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventIntegrationTest.java new file mode 100644 index 000000000000..8da04397648c --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventIntegrationTest.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.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.DeltaSession; + +@RunWith(JUnitParamsRunner.class) +public class DeltaSessionDestroyAttributeEventIntegrationTest + extends AbstractDeltaSessionIntegrationTest { + + @Test + public void toDataAndFromDataShouldWorkProperly() + throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException { + DeltaSessionDestroyAttributeEvent originalEvent = + new DeltaSessionDestroyAttributeEvent(FIRST_ATTRIBUTE_KEY); + DeltaSessionDestroyAttributeEvent deserializeEvent = + (DeltaSessionDestroyAttributeEvent) serializeDeserializeObject(originalEvent); + + assertThat(deserializeEvent.getAttributeName()).isEqualTo(originalEvent.getAttributeName()); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void applyShouldDestroyTheSessionAttributeFromTheLocalCacheEntry( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + DeltaSessionDestroyAttributeEvent destroyEvent = + new DeltaSessionDestroyAttributeEvent(FIRST_ATTRIBUTE_KEY); + + // Apply event and verify local session entry is modified. + DeltaSession deltaSessionInterface = (DeltaSession) httpSessionRegion.get(TEST_SESSION_ID); + destroyEvent.apply(deltaSessionInterface); + assertThat((DeltaSession) httpSessionRegion.get(TEST_SESSION_ID) + .getAttribute(FIRST_ATTRIBUTE_KEY)).isNull(); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsIntegrationTest.java new file mode 100644 index 000000000000..985eec360289 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsIntegrationTest.java @@ -0,0 +1,113 @@ +/* + * 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.internal; + +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.servlet.http.HttpSession; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.CustomExpiry; +import org.apache.geode.cache.ExpirationAction; +import org.apache.geode.cache.ExpirationAttributes; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.AbstractSessionCache; +import org.apache.geode.modules.session.catalina.SessionCache; +import org.apache.geode.modules.session.catalina.callback.SessionExpirationCacheListener; + +@RunWith(JUnitParamsRunner.class) +public class DeltaSessionStatisticsIntegrationTest extends AbstractDeltaSessionIntegrationTest { + private DeltaSessionStatistics statistics; + + @Before + public void setUp() { + statistics = new DeltaSessionStatistics(server.getCache().getDistributedSystem(), "testApp"); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void sessionsCreatedStatisticsShouldBeIncrementedWhenSessionIsAdded( + RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .create(REGION_NAME); + + SessionCache mockSessionCache = mock(AbstractSessionCache.class); + doCallRealMethod().when(mockSessionCache).putSession(any()); + when(mockSessionCache.getStatistics()).thenReturn(statistics); + when(mockSessionCache.getOperatingRegion()).thenReturn(httpSessionRegion); + + mockDeltaSessionManager(); + doCallRealMethod().when(deltaSessionManager).add(any()); + when(deltaSessionManager.getSessionCache()).thenReturn(mockSessionCache); + + deltaSessionManager.add(new TestDeltaSession(deltaSessionManager, TEST_SESSION_ID)); + await().untilAsserted(() -> assertThat(httpSessionRegion.isEmpty()).isFalse()); + assertThat(statistics.getSessionsCreated()).isEqualTo(1); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void sessionsExpiredStatisticsShouldBeIncrementedWhenSessionExpires( + RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .setCustomEntryTimeToLive(new TestCustomExpiry()) + .addCacheListener(new SessionExpirationCacheListener()) + .create(REGION_NAME); + mockDeltaSessionManager(); + when(deltaSessionManager.getStatistics()).thenReturn(statistics); + httpSessionRegion.put(TEST_SESSION_ID, + new TestDeltaSession(deltaSessionManager, TEST_SESSION_ID)); + + await().untilAsserted(() -> assertThat(httpSessionRegion.isEmpty()).isTrue()); + assertThat(statistics.getSessionsExpired()).isEqualTo(1); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void sessionsInvalidatedStatisticsShouldBeIncrementedWhenSessionInInvalidated( + RegionShortcut regionShortcut) { + httpSessionRegion = server.getCache() + .createRegionFactory(regionShortcut) + .create(REGION_NAME); + mockDeltaSessionManager(); + when(deltaSessionManager.getStatistics()).thenReturn(statistics); + TestDeltaSession deltaSession = new TestDeltaSession(deltaSessionManager, TEST_SESSION_ID); + httpSessionRegion.put(deltaSession.getId(), deltaSession); + + deltaSession.invalidate(); + await().untilAsserted(() -> assertThat(httpSessionRegion.isEmpty()).isTrue()); + assertThat(statistics.getSessionsInvalidated()).isEqualTo(1); + } + + static class TestCustomExpiry implements CustomExpiry { + @Override + public ExpirationAttributes getExpiry(Region.Entry entry) { + return new ExpirationAttributes(1, ExpirationAction.DESTROY); + } + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventIntegrationTest.java new file mode 100644 index 000000000000..de087d468a2d --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventIntegrationTest.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.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.modules.session.catalina.DeltaSession; + +@RunWith(JUnitParamsRunner.class) +public class DeltaSessionUpdateAttributeEventIntegrationTest + extends AbstractDeltaSessionIntegrationTest { + + @Test + public void toDataAndFromDataShouldWorkProperly() + throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + DeltaSessionUpdateAttributeEvent originalEvent = + new DeltaSessionUpdateAttributeEvent(FIRST_ATTRIBUTE_KEY, FIRST_ATTRIBUTE_VALUE); + DeltaSessionUpdateAttributeEvent deserializeEvent = + (DeltaSessionUpdateAttributeEvent) serializeDeserializeObject(originalEvent); + + assertThat(deserializeEvent.getAttributeName()).isEqualTo(originalEvent.getAttributeName()); + assertThat(deserializeEvent.getAttributeValue()).isEqualTo(originalEvent.getAttributeValue()); + } + + @Test + @Parameters({"REPLICATE", "PARTITION"}) + public void applyShouldUpdateTheSessionAttributeWithinTheLocalCacheEntry( + RegionShortcut regionShortcut) { + parameterizedSetUp(regionShortcut); + DeltaSessionUpdateAttributeEvent updateAttributeEvent = + new DeltaSessionUpdateAttributeEvent(FIRST_ATTRIBUTE_KEY, SECOND_ATTRIBUTE_VALUE); + + // Apply event and verify local session entry is modified. + DeltaSession deltaSessionInterface = (DeltaSession) httpSessionRegion.get(TEST_SESSION_ID); + updateAttributeEvent.apply(deltaSessionInterface); + assertThat(httpSessionRegion.get(TEST_SESSION_ID).getAttribute(FIRST_ATTRIBUTE_KEY)) + .isEqualTo(SECOND_ATTRIBUTE_VALUE); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java deleted file mode 100644 index 3eaa3f71ad61..000000000000 --- a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java +++ /dev/null @@ -1,131 +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.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.junit.Test; -import org.junit.experimental.categories.Category; - -import org.apache.geode.cache.execute.FunctionContext; -import org.apache.geode.cache.execute.ResultSender; -import org.apache.geode.distributed.internal.ClusterDistributionManager; -import org.apache.geode.distributed.internal.DistributionManager; -import org.apache.geode.distributed.internal.InternalDistributedSystem; -import org.apache.geode.distributed.internal.membership.InternalDistributedMember; -import org.apache.geode.internal.cache.GemFireCacheImpl; -import org.apache.geode.test.junit.categories.SecurityTest; - -@Category(SecurityTest.class) -public class BootstrappingFunctionTest { - - @Test - public void isLocatorReturnsTrueForLocatorMember() { - BootstrappingFunction bootstrappingFunction = new BootstrappingFunction(); - GemFireCacheImpl gemFireCacheImpl = mock(GemFireCacheImpl.class); - InternalDistributedSystem distributedSystem = mock(InternalDistributedSystem.class); - InternalDistributedMember internalDistributedMember = mock(InternalDistributedMember.class); - DistributionManager distributionManager = mock(DistributionManager.class); - - when(gemFireCacheImpl.getDistributedSystem()).thenReturn(distributedSystem); - when(distributedSystem.getDistributionManager()).thenReturn(distributionManager); - when(distributedSystem.getDistributedMember()).thenReturn(internalDistributedMember); - when(internalDistributedMember.getVmKind()) - .thenReturn(ClusterDistributionManager.LOCATOR_DM_TYPE); - - assertThat(bootstrappingFunction.isLocator(gemFireCacheImpl)).isTrue(); - } - - @Test - public void isLocatorReturnsFalseForNonLocatorMember() { - BootstrappingFunction bootstrappingFunction = new BootstrappingFunction(); - GemFireCacheImpl gemFireCacheImpl = mock(GemFireCacheImpl.class); - InternalDistributedSystem distributedSystem = mock(InternalDistributedSystem.class); - InternalDistributedMember internalDistributedMember = mock(InternalDistributedMember.class); - DistributionManager distributionManager = mock(DistributionManager.class); - - when(gemFireCacheImpl.getDistributedSystem()).thenReturn(distributedSystem); - when(distributedSystem.getDistributionManager()).thenReturn(distributionManager); - when(distributedSystem.getDistributedMember()).thenReturn(internalDistributedMember); - when(internalDistributedMember.getVmKind()) - .thenReturn(ClusterDistributionManager.NORMAL_DM_TYPE); - - assertThat(bootstrappingFunction.isLocator(gemFireCacheImpl)).isFalse(); - } - - @Test - public void registerFunctionIsNotCalledOnLocator() { - BootstrappingFunction bootstrappingFunction = spy(new BootstrappingFunction()); - GemFireCacheImpl gemFireCacheImpl = mock(GemFireCacheImpl.class); - InternalDistributedSystem distributedSystem = mock(InternalDistributedSystem.class); - InternalDistributedMember internalDistributedMember = mock(InternalDistributedMember.class); - DistributionManager distributionManager = mock(DistributionManager.class); - - when(bootstrappingFunction.verifyCacheExists()).thenReturn(gemFireCacheImpl); - when(gemFireCacheImpl.getDistributedSystem()).thenReturn(distributedSystem); - when(distributedSystem.getDistributionManager()).thenReturn(distributionManager); - when(distributedSystem.getDistributedMember()).thenReturn(internalDistributedMember); - when(internalDistributedMember.getVmKind()) - .thenReturn(ClusterDistributionManager.LOCATOR_DM_TYPE); - doNothing().when(distributionManager).addMembershipListener(bootstrappingFunction); - - FunctionContext functionContext = mock(FunctionContext.class); - @SuppressWarnings("unchecked") - ResultSender resultSender = (ResultSender) mock(ResultSender.class); - doNothing().when(resultSender).lastResult(any()); - - when(functionContext.getResultSender()).thenReturn(resultSender); - - bootstrappingFunction.execute(functionContext); - - verify(bootstrappingFunction, never()).registerFunctions(); - } - - @Test - public void registerFunctionGetsCalledOnNonLocators() { - BootstrappingFunction bootstrappingFunction = spy(new BootstrappingFunction()); - GemFireCacheImpl gemFireCacheImpl = mock(GemFireCacheImpl.class); - InternalDistributedSystem distributedSystem = mock(InternalDistributedSystem.class); - InternalDistributedMember internalDistributedMember = mock(InternalDistributedMember.class); - DistributionManager distributionManager = mock(DistributionManager.class); - - when(bootstrappingFunction.verifyCacheExists()).thenReturn(gemFireCacheImpl); - when(gemFireCacheImpl.getDistributedSystem()).thenReturn(distributedSystem); - when(distributedSystem.getDistributionManager()).thenReturn(distributionManager); - when(distributedSystem.getDistributedMember()).thenReturn(internalDistributedMember); - when(internalDistributedMember.getVmKind()) - .thenReturn(ClusterDistributionManager.NORMAL_DM_TYPE); - doNothing().when(distributionManager).addMembershipListener(bootstrappingFunction); - - FunctionContext functionContext = mock(FunctionContext.class); - @SuppressWarnings("unchecked") - ResultSender resultSender = (ResultSender) mock(ResultSender.class); - doNothing().when(resultSender).lastResult(any()); - - when(functionContext.getResultSender()).thenReturn(resultSender); - - bootstrappingFunction.execute(functionContext); - - verify(bootstrappingFunction, times(1)).registerFunctions(); - } -} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionIntegrationTest.java new file mode 100644 index 000000000000..a12245a03839 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionIntegrationTest.java @@ -0,0 +1,112 @@ +/* + * 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.util; + +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.execute.Execution; +import org.apache.geode.cache.execute.FunctionService; +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.internal.cache.InternalRegion; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +public class TouchPartitionedRegionEntriesFunctionIntegrationTest { + private static final String REGION_NAME = "testRegion"; + private Region region; + private Map originalAccessedTime = new HashMap<>(); + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + @Before + public void setUp() { + region = server.getCache() + .createRegionFactory(RegionShortcut.PARTITION) + .setStatisticsEnabled(true) + .create(REGION_NAME); + + IntStream.range(0, 10).forEach(i -> { + String key = "key_" + i; + region.put(key, "value_" + i); + + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + originalAccessedTime.put(key, laTime); + }); + + FunctionService.registerFunction(new TouchPartitionedRegionEntriesFunction()); + } + + @Test + public void executeShouldDoNothingWhenFilterIsEmpty() { + // Execute function with empty keys set. + @SuppressWarnings("unchecked") + Execution execution = FunctionService + .onRegion(region).withFilter(new HashSet<>(Collections.singletonList(""))); + ResultCollector collector = execution.execute(new TouchPartitionedRegionEntriesFunction()); + + // Verify function results + assertThat(collector.getResult()).isEqualTo(Collections.singletonList(Boolean.TRUE)); + + // Verify lastAccessedTime did not change + originalAccessedTime.forEach((key, savedLastAccessedTime) -> { + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + assertThat(laTime - savedLastAccessedTime).isEqualTo(0); + }); + } + + @Test + public void executeShouldUpdateEntryLastAccessedTimeForKeysPresentInTheFilter() { + Set keysToTouch = new HashSet<>(Arrays.asList("key_1", "key_5", "key_3")); + + // Wait until time passes. + long currentTime = System.nanoTime(); + await().untilAsserted(() -> assertThat(System.nanoTime()).isGreaterThan(currentTime)); + + // Execute function on specific keys. + @SuppressWarnings("unchecked") + Execution execution = FunctionService + .onRegion(region).withFilter(keysToTouch); + ResultCollector collector = execution.execute(new TouchPartitionedRegionEntriesFunction()); + + // Verify function results + assertThat(collector.getResult()).isEqualTo(Collections.singletonList(Boolean.TRUE)); + + // Verify lastAccessedTime changed for touched keys + originalAccessedTime.forEach((key, savedLastAccessedTime) -> { + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + + if (!keysToTouch.contains(key)) { + assertThat(laTime - savedLastAccessedTime).isEqualTo(0); + } else { + assertThat(laTime - savedLastAccessedTime).isNotEqualTo(0); + } + }); + } +} diff --git a/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionIntegrationTest.java b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionIntegrationTest.java new file mode 100644 index 000000000000..15baf6e22460 --- /dev/null +++ b/extensions/geode-modules/src/integrationTest/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionIntegrationTest.java @@ -0,0 +1,121 @@ +/* + * 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.util; + +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.execute.Execution; +import org.apache.geode.cache.execute.FunctionService; +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.internal.cache.InternalRegion; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +public class TouchReplicatedRegionEntriesFunctionIntegrationTest { + private static final String REGION_NAME = "testRegion"; + private Region region; + private Map originalAccessedTime = new HashMap<>(); + + @Rule + public ServerStarterRule server = new ServerStarterRule().withAutoStart(); + + @Before + public void setUp() { + region = server.getCache() + .createRegionFactory(RegionShortcut.REPLICATE) + .setStatisticsEnabled(true) + .create(REGION_NAME); + + IntStream.range(0, 20).forEach(i -> { + String key = "key_" + i; + region.put(key, "value_" + i); + + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + originalAccessedTime.put(key, laTime); + }); + + FunctionService.registerFunction(new TouchReplicatedRegionEntriesFunction()); + } + + private void executeFunctionAndAssertLastAccessedTimeIsNotUpdated(Object[] args) { + @SuppressWarnings("unchecked") + Execution execution = FunctionService.onMembers().setArguments(args); + ResultCollector collector = execution.execute(new TouchReplicatedRegionEntriesFunction()); + + // Verify function results + assertThat(collector.getResult()).isEqualTo(Collections.singletonList(Boolean.TRUE)); + + // Verify lastAccessedTime changed for touched keys + originalAccessedTime.forEach((key, savedLastAccessedTime) -> { + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + assertThat(laTime - savedLastAccessedTime).isEqualTo(0); + }); + } + + @Test + public void executeShouldDoNothingWhenRegionDoesNotExist() { + Object[] arguments = new Object[] {"/nonExistingRegion", null}; + executeFunctionAndAssertLastAccessedTimeIsNotUpdated(arguments); + } + + @Test + public void executeShouldDoNothingWhenKeySetIsEmpty() { + Object[] arguments = new Object[] {REGION_NAME, Collections.emptySet()}; + executeFunctionAndAssertLastAccessedTimeIsNotUpdated(arguments); + } + + @Test + public void executeShouldUpdateEntryLastAccessedTimeForKeysPresentInTheFilter() { + Set keysToTouch = new HashSet<>(Arrays.asList("key_1", "key_5", "key_3")); + Object[] args = new Object[] {REGION_NAME, keysToTouch}; + + // Wait until time passes. + long currentTime = System.nanoTime(); + await().untilAsserted(() -> assertThat(System.nanoTime()).isGreaterThan(currentTime)); + + // Execute function on specific keys. + @SuppressWarnings("unchecked") + Execution execution = FunctionService.onMembers().setArguments(args); + ResultCollector collector = execution.execute(new TouchReplicatedRegionEntriesFunction()); + + // Verify function results + assertThat(collector.getResult()).isEqualTo(Collections.singletonList(Boolean.TRUE)); + + // Verify lastAccessedTime changed for touched keys + originalAccessedTime.forEach((key, savedLastAccessedTime) -> { + Long laTime = ((InternalRegion) region).getEntry(key).getStatistics().getLastAccessedTime(); + + if (!keysToTouch.contains(key)) { + assertThat(laTime - savedLastAccessedTime).isEqualTo(0); + } else { + assertThat(laTime - savedLastAccessedTime).isNotEqualTo(0); + } + }); + } +} diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatch.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatch.java index a254f7a8634e..315f1ef262ae 100644 --- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatch.java +++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatch.java @@ -28,9 +28,12 @@ @SuppressWarnings("serial") public class DeltaSessionAttributeEventBatch extends AbstractGatewayDeltaEvent { - private List eventQueue; + List getEventQueue() { + return eventQueue; + } + @SuppressWarnings("unused") public DeltaSessionAttributeEventBatch() {} diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEvent.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEvent.java index c4f7d2f6e4fa..f30e0c9b9806 100644 --- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEvent.java +++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEvent.java @@ -25,6 +25,10 @@ public class DeltaSessionDestroyAttributeEvent implements DeltaSessionAttributeEvent { private String attributeName; + String getAttributeName() { + return attributeName; + } + @SuppressWarnings("unused") public DeltaSessionDestroyAttributeEvent() {} diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEvent.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEvent.java index cba5ff682bbb..4be55a64c4e0 100644 --- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEvent.java +++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEvent.java @@ -26,6 +26,14 @@ public class DeltaSessionUpdateAttributeEvent implements DeltaSessionAttributeEv private String attributeName; private Object attributeValue; + String getAttributeName() { + return attributeName; + } + + Object getAttributeValue() { + return attributeValue; + } + @SuppressWarnings("unused") public DeltaSessionUpdateAttributeEvent() {} diff --git a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java new file mode 100644 index 000000000000..abb83bc87fb8 --- /dev/null +++ b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/BootstrappingFunctionTest.java @@ -0,0 +1,102 @@ +/* + * 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.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.execute.FunctionContext; +import org.apache.geode.cache.execute.ResultSender; +import org.apache.geode.distributed.internal.ClusterDistributionManager; +import org.apache.geode.distributed.internal.DistributionManager; +import org.apache.geode.distributed.internal.InternalDistributedSystem; +import org.apache.geode.distributed.internal.membership.InternalDistributedMember; + +public class BootstrappingFunctionTest { + private Cache mockCache; + private DistributionManager distributionManager; + private BootstrappingFunction bootstrappingFunction; + private InternalDistributedMember distributedMember; + + @Before + public void setUp() { + mockCache = mock(Cache.class); + bootstrappingFunction = spy(new BootstrappingFunction()); + distributedMember = mock(InternalDistributedMember.class); + distributionManager = mock(DistributionManager.class); + + InternalDistributedSystem distributedSystem = mock(InternalDistributedSystem.class); + when(distributedSystem.getDistributedMember()).thenReturn(distributedMember); + when(distributedSystem.getDistributionManager()).thenReturn(distributionManager); + when(mockCache.getDistributedSystem()).thenReturn(distributedSystem); + } + + @Test + public void isLocatorReturnsTrueForLocatorMember() { + when(distributedMember.getVmKind()).thenReturn(ClusterDistributionManager.LOCATOR_DM_TYPE); + + assertThat(bootstrappingFunction.isLocator(mockCache)).isTrue(); + } + + @Test + public void isLocatorReturnsFalseForNonLocatorMember() { + when(distributedMember.getVmKind()).thenReturn(ClusterDistributionManager.NORMAL_DM_TYPE); + + assertThat(bootstrappingFunction.isLocator(mockCache)).isFalse(); + } + + @Test + public void registerFunctionIsNotCalledOnLocator() { + when(bootstrappingFunction.verifyCacheExists()).thenReturn(mockCache); + when(distributedMember.getVmKind()).thenReturn(ClusterDistributionManager.LOCATOR_DM_TYPE); + doNothing().when(distributionManager).addMembershipListener(bootstrappingFunction); + + @SuppressWarnings("unchecked") + ResultSender resultSender = (ResultSender) mock(ResultSender.class); + FunctionContext functionContext = mock(FunctionContext.class); + doNothing().when(resultSender).lastResult(any()); + when(functionContext.getResultSender()).thenReturn(resultSender); + + bootstrappingFunction.execute(functionContext); + verify(bootstrappingFunction, never()).registerFunctions(); + } + + @Test + public void registerFunctionGetsCalledOnNonLocators() { + when(bootstrappingFunction.verifyCacheExists()).thenReturn(mockCache); + when(distributedMember.getVmKind()).thenReturn(ClusterDistributionManager.NORMAL_DM_TYPE); + doNothing().when(distributionManager).addMembershipListener(bootstrappingFunction); + + @SuppressWarnings("unchecked") + ResultSender resultSender = (ResultSender) mock(ResultSender.class); + FunctionContext functionContext = mock(FunctionContext.class); + doNothing().when(resultSender).lastResult(any()); + when(functionContext.getResultSender()).thenReturn(resultSender); + + bootstrappingFunction.execute(functionContext); + verify(bootstrappingFunction, times(1)).registerFunctions(); + } +}