/*
* Copyright 2011-2017 the original author or authors.
*
* Licensed 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.glowroot.agent.plugin.servlet;
import java.io.IOException;
import java.util.Iterator;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.common.base.Stopwatch;
import com.ning.http.client.AsyncHttpClient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.glowroot.agent.it.harness.AppUnderTest;
import org.glowroot.agent.it.harness.Container;
import org.glowroot.agent.it.harness.Containers;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
public class ServletDispatcherIT {
private static Container container;
@BeforeClass
public static void setUp() throws Exception {
container = Containers.create();
}
@AfterClass
public static void tearDown() throws Exception {
container.close();
}
@After
public void afterEachTest() throws Exception {
container.checkAndReset();
}
@Test
public void testForwardServlet() throws Exception {
testForwardServlet("", InvokeForwardServlet.class);
}
@Test
public void testForwardServletWithContextPath() throws Exception {
testForwardServlet("/zzz", InvokeForwardServletWithContextPath.class);
}
@Test
public void testForwardServletUsingContext() throws Exception {
testForwardServletUsingContext("", InvokeForwardServletUsingContext.class);
}
@Test
public void testForwardServletUsingContextWithContextPath() throws Exception {
testForwardServletUsingContext("/zzz",
InvokeForwardServletUsingContextWithContextPath.class);
}
@Test
public void testForwardServletUsingNamed() throws Exception {
testForwardServletUsingNamed("", InvokeForwardServletUsingNamed.class);
}
@Test
public void testForwardServletUsingNamedWithContextPath() throws Exception {
testForwardServletUsingNamed("/zzz", InvokeForwardServletUsingNamedWithContextPath.class);
}
@Test
public void testIncludeServlet() throws Exception {
testIncludeServlet("", InvokeIncludeServlet.class);
}
@Test
public void testIncludeServletWithContextPath() throws Exception {
testIncludeServlet("/zzz", InvokeIncludeServletWithContextPath.class);
}
private void testForwardServlet(String contextPath,
Class<? extends AppUnderTest> appUnderTestClass) throws Exception {
// when
Trace trace = container.execute(appUnderTestClass);
Stopwatch stopwatch = Stopwatch.createStarted();
while (hasServletInit(trace) && stopwatch.elapsed(SECONDS) < 10) {
trace = container.execute(appUnderTestClass);
}
if (hasServletInit(trace)) {
throw new AssertionError("Timed out waiting for the real trace");
}
// then
Trace.Header header = trace.getHeader();
assertThat(header.getHeadline()).isEqualTo(contextPath + "/first-forward");
assertThat(header.getTransactionName()).isEqualTo(contextPath + "/first-forward");
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("servlet dispatch: /second");
assertThat(i.hasNext()).isFalse();
}
private void testForwardServletUsingContext(String contextPath,
Class<? extends AppUnderTest> appUnderTestClass) throws Exception {
// when
Trace trace = container.execute(appUnderTestClass);
Stopwatch stopwatch = Stopwatch.createStarted();
while (hasServletInit(trace) && stopwatch.elapsed(SECONDS) < 10) {
trace = container.execute(appUnderTestClass);
}
if (hasServletInit(trace)) {
throw new AssertionError("Timed out waiting for the real trace");
}
// then
Trace.Header header = trace.getHeader();
assertThat(header.getHeadline()).isEqualTo(contextPath + "/first-forward-using-context");
assertThat(header.getTransactionName())
.isEqualTo(contextPath + "/first-forward-using-context");
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("servlet dispatch: /second");
assertThat(i.hasNext()).isFalse();
}
private void testForwardServletUsingNamed(String contextPath,
Class<? extends AppUnderTest> appUnderTestClass) throws Exception {
// when
Trace trace = container.execute(appUnderTestClass);
Stopwatch stopwatch = Stopwatch.createStarted();
while (hasServletInit(trace) && stopwatch.elapsed(SECONDS) < 10) {
trace = container.execute(appUnderTestClass);
}
if (hasServletInit(trace)) {
throw new AssertionError("Timed out waiting for the real trace");
}
// then
Trace.Header header = trace.getHeader();
assertThat(header.getHeadline()).isEqualTo(contextPath + "/first-forward-using-named");
assertThat(header.getTransactionName())
.isEqualTo(contextPath + "/first-forward-using-named");
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("servlet dispatch: yyy");
assertThat(i.hasNext()).isFalse();
}
private void testIncludeServlet(String contextPath,
Class<? extends AppUnderTest> appUnderTestClass) throws Exception {
// when
Trace trace = container.execute(appUnderTestClass);
Stopwatch stopwatch = Stopwatch.createStarted();
while (hasServletInit(trace) && stopwatch.elapsed(SECONDS) < 10) {
trace = container.execute(appUnderTestClass);
}
if (hasServletInit(trace)) {
throw new AssertionError("Timed out waiting for the real trace");
}
// then
Trace.Header header = trace.getHeader();
assertThat(header.getHeadline()).isEqualTo(contextPath + "/first-include");
assertThat(header.getTransactionName()).isEqualTo(contextPath + "/first-include");
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("servlet dispatch: /second");
assertThat(i.hasNext()).isFalse();
}
private static boolean hasServletInit(Trace trace) {
Trace.Entry lastEntry = trace.getEntry(trace.getEntryCount() - 1);
return lastEntry.getMessage().startsWith("servlet init: ");
}
public static class InvokeForwardServlet extends InvokeForwardServletBase {
public InvokeForwardServlet() {
super("");
}
}
public static class InvokeForwardServletWithContextPath extends InvokeForwardServletBase {
public InvokeForwardServletWithContextPath() {
super("/zzz");
}
}
private static class InvokeForwardServletBase extends InvokeServletInTomcat {
private InvokeForwardServletBase(String contextPath) {
super(contextPath);
}
@Override
protected void doTest(int port) throws Exception {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
// send initial to trigger servlet init methods so they don't end up in trace
int statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath + "/first-forward")
.execute().get().getStatusCode();
if (statusCode != 200) {
asyncHttpClient.close();
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath + "/first-forward")
.execute().get().getStatusCode();
asyncHttpClient.close();
if (statusCode != 200) {
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
}
}
public static class InvokeForwardServletUsingContext
extends InvokeForwardServletUsingContextBase {
public InvokeForwardServletUsingContext() {
super("");
}
}
public static class InvokeForwardServletUsingContextWithContextPath
extends InvokeForwardServletUsingContextBase {
public InvokeForwardServletUsingContextWithContextPath() {
super("/zzz");
}
}
private static class InvokeForwardServletUsingContextBase extends InvokeServletInTomcat {
private InvokeForwardServletUsingContextBase(String contextPath) {
super(contextPath);
}
@Override
protected void doTest(int port) throws Exception {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
// send initial to trigger servlet init methods so they don't end up in trace
int statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath
+ "/first-forward-using-context")
.execute().get().getStatusCode();
if (statusCode != 200) {
asyncHttpClient.close();
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath
+ "/first-forward-using-context")
.execute().get().getStatusCode();
asyncHttpClient.close();
if (statusCode != 200) {
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
}
}
public static class InvokeForwardServletUsingNamed extends InvokeForwardServletUsingNamedBase {
public InvokeForwardServletUsingNamed() {
super("");
}
}
public static class InvokeForwardServletUsingNamedWithContextPath
extends InvokeForwardServletUsingNamedBase {
public InvokeForwardServletUsingNamedWithContextPath() {
super("/zzz");
}
}
private static class InvokeForwardServletUsingNamedBase extends InvokeServletInTomcat {
private InvokeForwardServletUsingNamedBase(String contextPath) {
super(contextPath);
}
@Override
protected void doTest(int port) throws Exception {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
// send initial to trigger servlet init methods so they don't end up in trace
int statusCode = asyncHttpClient
.prepareGet(
"http://localhost:" + port + contextPath + "/first-forward-using-named")
.execute().get().getStatusCode();
if (statusCode != 200) {
asyncHttpClient.close();
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
statusCode = asyncHttpClient
.prepareGet(
"http://localhost:" + port + contextPath + "/first-forward-using-named")
.execute().get().getStatusCode();
asyncHttpClient.close();
if (statusCode != 200) {
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
}
}
public static class InvokeIncludeServlet extends InvokeIncludeServletBase {
public InvokeIncludeServlet() {
super("");
}
}
public static class InvokeIncludeServletWithContextPath extends InvokeIncludeServletBase {
public InvokeIncludeServletWithContextPath() {
super("/zzz");
}
}
private static class InvokeIncludeServletBase extends InvokeServletInTomcat {
private InvokeIncludeServletBase(String contextPath) {
super(contextPath);
}
@Override
protected void doTest(int port) throws Exception {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
// send initial to trigger servlet init methods so they don't end up in trace
int statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath + "/first-include")
.execute().get().getStatusCode();
if (statusCode != 200) {
asyncHttpClient.close();
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
statusCode = asyncHttpClient
.prepareGet("http://localhost:" + port + contextPath + "/first-include")
.execute().get().getStatusCode();
asyncHttpClient.close();
if (statusCode != 200) {
throw new IllegalStateException("Unexpected status code: " + statusCode);
}
}
}
@WebServlet("/first-forward")
@SuppressWarnings("serial")
public static class FirstForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/second").forward(request, response);
}
}
@WebServlet("/first-forward-using-context")
@SuppressWarnings("serial")
public static class FirstForwardUsingContextServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
getServletContext().getRequestDispatcher("/second").forward(request, response);
}
}
@WebServlet("/first-forward-using-named")
@SuppressWarnings("serial")
public static class FirstForwardUsingNamedServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
getServletContext().getNamedDispatcher("yyy").forward(request, response);
}
}
@WebServlet("/first-include")
@SuppressWarnings("serial")
public static class FirstIncludeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/second").include(request, response);
}
}
@WebServlet(urlPatterns = "/second", name = "yyy")
@SuppressWarnings("serial")
public static class SecondServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().print("second");
}
}
}