/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.clustering.cluster.web.passivation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.Formatter; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.stream.Stream; import javax.servlet.http.HttpServletResponse; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.HttpClientUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.junit.InSequence; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.as.test.clustering.cluster.ClusterAbstractTestCase; import org.jboss.as.test.clustering.cluster.web.DistributableTestCase; import org.jboss.as.test.http.util.TestHttpClientUtils; import org.jboss.as.test.shared.TimeoutUtil; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Test; public abstract class SessionPassivationTestCase extends ClusterAbstractTestCase { private static final int MAX_PASSIVATION_WAIT = TimeoutUtil.adjust(10000); static WebArchive getBaseDeployment() { WebArchive war = ShrinkWrap.create(WebArchive.class, "passivation.war"); war.addClasses(SessionOperationServlet.class); // Take web.xml from the managed test. war.setWebXML(DistributableTestCase.class.getPackage(), "web.xml"); return war; } @Test @InSequence(1) public void test(@ArquillianResource(SessionOperationServlet.class) @OperateOnDeployment(DEPLOYMENT_1) URL baseURL) throws IOException, URISyntaxException { String session1 = null; String session2 = null; try (CloseableHttpClient client1 = TestHttpClientUtils.promiscuousCookieHttpClient(); CloseableHttpClient client2 = TestHttpClientUtils.promiscuousCookieHttpClient()) { // This should not trigger any passivation/activation events HttpResponse response = client1.execute(new HttpGet(SessionOperationServlet.createSetURI(baseURL, "a", "1"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); session1 = getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID); } finally { HttpClientUtils.closeQuietly(response); } long now = System.currentTimeMillis(); long start = now; Map<String, Queue<SessionOperationServlet.EventType>> events = new HashMap<>(); Map<String, SessionOperationServlet.EventType> expectedEvents = new HashMap<>(); events.put(session1, new LinkedList<>()); expectedEvents.put(session1, SessionOperationServlet.EventType.PASSIVATION); // This will trigger passivation of session1 response = client2.execute(new HttpGet(SessionOperationServlet.createSetURI(baseURL, "a", "2"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); session2 = getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID); events.put(session2, new LinkedList<>()); expectedEvents.put(session2, SessionOperationServlet.EventType.PASSIVATION); collectEvents(response, events); } finally { HttpClientUtils.closeQuietly(response); } // Ensure session1 was passivated while (events.get(session1).isEmpty() && ((now - start) < MAX_PASSIVATION_WAIT)) { response = client2.execute(new HttpGet(SessionOperationServlet.createGetURI(baseURL, "a"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); assertEquals(session2, getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID)); assertEquals("2", getRequiredHeaderValue(response, SessionOperationServlet.RESULT)); collectEvents(response, events); } finally { HttpClientUtils.closeQuietly(response); } Thread.yield(); now = System.currentTimeMillis(); } assertFalse(events.get(session1).isEmpty()); validateEvents(session1, events, expectedEvents); now = System.currentTimeMillis(); start = now; // This should trigger activation of session1 and passivation of session2 response = client1.execute(new HttpGet(SessionOperationServlet.createGetURI(baseURL, "a"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); assertEquals(session1, getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID)); assertEquals("1", getRequiredHeaderValue(response, SessionOperationServlet.RESULT)); collectEvents(response, events); assertFalse(events.get(session1).isEmpty()); assertTrue(events.get(session1).contains(SessionOperationServlet.EventType.ACTIVATION)); } finally { HttpClientUtils.closeQuietly(response); } // Verify session2 was passivated while (events.get(session2).isEmpty() && ((now - start) < MAX_PASSIVATION_WAIT)) { response = client1.execute(new HttpGet(SessionOperationServlet.createGetURI(baseURL, "a"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); assertEquals(session1, getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID)); assertEquals("1", getRequiredHeaderValue(response, SessionOperationServlet.RESULT)); collectEvents(response, events); } finally { HttpClientUtils.closeQuietly(response); } Thread.yield(); now = System.currentTimeMillis(); } assertFalse(events.get(session2).isEmpty()); validateEvents(session2, events, expectedEvents); now = System.currentTimeMillis(); start = now; // This should trigger activation of session2 and passivation of session1 response = client2.execute(new HttpGet(SessionOperationServlet.createGetURI(baseURL, "a"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); assertEquals(session2, getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID)); assertEquals("2", getRequiredHeaderValue(response, SessionOperationServlet.RESULT)); collectEvents(response, events); assertFalse(events.get(session2).isEmpty()); assertTrue(events.get(session2).contains(SessionOperationServlet.EventType.ACTIVATION)); } finally { HttpClientUtils.closeQuietly(response); } // Verify session1 was passivated while (!events.get(session1).isEmpty() && ((now - start) < MAX_PASSIVATION_WAIT)) { response = client2.execute(new HttpGet(SessionOperationServlet.createGetURI(baseURL, "a"))); try { assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode()); assertEquals(session2, getRequiredHeaderValue(response, SessionOperationServlet.SESSION_ID)); assertEquals("2", getRequiredHeaderValue(response, SessionOperationServlet.RESULT)); collectEvents(response, events); } finally { HttpClientUtils.closeQuietly(response); } Thread.yield(); now = System.currentTimeMillis(); } assertFalse(events.get(session1).isEmpty()); validateEvents(session1, events, expectedEvents); validateEvents(session2, events, expectedEvents); } } private static void collectEvents(HttpResponse response, Map<String, Queue<SessionOperationServlet.EventType>> events) { events.entrySet().forEach((Map.Entry<String, Queue<SessionOperationServlet.EventType>> entry) -> { String sessionId = entry.getKey(); if (response.containsHeader(sessionId)) { Stream.of(response.getHeaders(sessionId)).forEach((Header header) -> { entry.getValue().add(SessionOperationServlet.EventType.valueOf(header.getValue())); }); } }); } private static void validateEvents(String sessionId, Map<String, Queue<SessionOperationServlet.EventType>> events, Map<String, SessionOperationServlet.EventType> expectedEvents) { Queue<SessionOperationServlet.EventType> types = events.get(sessionId); SessionOperationServlet.EventType type = types.poll(); SessionOperationServlet.EventType expected = expectedEvents.get(sessionId); while (type != null) { assertSame(expected, type); type = types.poll(); // ACTIVATE event must follow PASSIVATE event and vice versa expected = SessionOperationServlet.EventType.values()[(expected.ordinal() + 1) % 2]; } expectedEvents.put(sessionId, expected); } private static String getRequiredHeaderValue(HttpResponse response, String name) { assertTrue(String.format("response doesn't contain header '%s', all response headers = %s", name, showHeaders(response.getAllHeaders())), response.containsHeader(name)); return response.getFirstHeader(name).getValue(); } private static String showHeaders(Header[] headers) { StringBuilder stringBuilder = new StringBuilder(); try (Formatter result = new Formatter(stringBuilder)) { Stream.of(headers).forEach((Header header) -> result.format("{name=%s, value=%s}, ", header.getName(), header.getValue())); return result.toString(); } } }