diff --git a/README.md b/README.md index 34dc9c2d4..774f0dab5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ List of supported frameworks with additional capabilities: | [Spark Web Framework](https://github.com/perwendel/spark) | 2.3+ | | [Spring Webflux](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/package-summary.html) | 5.0+ | | [Vert.x](https://vertx.io) | 3.0+ | +| [Struts](https://struts.apache.org/) | 2.3+ | ### Adding custom filter implementation diff --git a/instrumentation/struts-2.3/build.gradle.kts b/instrumentation/struts-2.3/build.gradle.kts new file mode 100644 index 000000000..27ff5c03d --- /dev/null +++ b/instrumentation/struts-2.3/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + `java-library` + id("io.opentelemetry.instrumentation.auto-instrumentation") + muzzle +} + +muzzle { + pass { + group = "org.apache.struts" + module = "struts2-core" + versions = "[2.3.1,)" + } +} + +val versions: Map by extra + +dependencies { + testImplementation(project(":instrumentation:servlet:servlet-rw")) + testImplementation(project(":instrumentation:servlet:servlet-3.0-no-wrapping")) + testImplementation(project(":testing-common")) { + exclude(group ="org.eclipse.jetty", module= "jetty-server") + } + testImplementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-3.0:${versions["opentelemetry_java_agent"]}") + testImplementation("org.apache.struts:struts2-core:2.3.1") + testImplementation("org.apache.struts:struts2-json-plugin:2.3.1") + testImplementation("org.apache.struts:struts2-convention-plugin:2.3.1") + testImplementation("org.eclipse.jetty:jetty-server:8.0.0.v20110901") + testImplementation("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") + testImplementation("javax.servlet:javax.servlet-api:3.0.1") + testImplementation("javax.servlet:jsp-api:2.0") +} diff --git a/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/Struts2Action.java b/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/Struts2Action.java new file mode 100644 index 000000000..8acff7764 --- /dev/null +++ b/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/Struts2Action.java @@ -0,0 +1,44 @@ +/* + * Copyright The Hypertrace 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 io.opentelemetry.javaagent.instrumentation.hypertrace.struts; + +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.convention.annotation.ParentPackage; +import org.apache.struts2.convention.annotation.Result; + +@Result(type = "json") +@ParentPackage("json-default") +public class Struts2Action extends ActionSupport { + + private String jsonString = "{'balance':1000.21,'is_vip':true,'num':100,'name':'foo'}"; + + public String body() { + return "body"; + } + + public String headers() { + return "headers"; + } + + public String getJsonString() { + return jsonString; + } + + public void setJsonString(String jsonString) { + this.jsonString = jsonString; + } +} diff --git a/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/StrutsInstrumentationTest.java b/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/StrutsInstrumentationTest.java new file mode 100644 index 000000000..627e3e4f5 --- /dev/null +++ b/instrumentation/struts-2.3/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/struts/StrutsInstrumentationTest.java @@ -0,0 +1,156 @@ +/* + * Copyright The Hypertrace 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 io.opentelemetry.javaagent.instrumentation.hypertrace.struts; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.TimeoutException; +import javax.servlet.DispatcherType; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.resource.FileResource; +import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; +import org.hypertrace.agent.testing.AbstractInstrumenterTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class StrutsInstrumentationTest extends AbstractInstrumenterTest { + + private static final String REQUEST_BODY = "hello"; + private static final String REQUEST_HEADER = "requestheader"; + private static final String REQUEST_HEADER_VALUE = "requestvalue"; + private static final String RESPONSE_HEADER = "headerName"; + private static final String RESPONSE_HEADER_VALUE = "headerValue"; + + private static Server server = new Server(0); + private static int serverPort; + + @BeforeAll + public static void startServer() throws Exception { + ServletContextHandler handler = new ServletContextHandler(); + FileResource resource = new FileResource(StrutsInstrumentationTest.class.getResource("/")); + handler.setContextPath("/context"); + handler.setBaseResource(resource); + handler.addServlet(DefaultServlet.class, "/"); + handler.addFilter( + StrutsPrepareAndExecuteFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); + server.setHandler(handler); + server.start(); + serverPort = server.getConnectors()[0].getLocalPort(); + } + + @AfterAll + public static void stopServer() throws Exception { + server.stop(); + } + + @Test + public void postUrlEncoded() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/context/body", serverPort)) + .post( + RequestBody.create( + REQUEST_BODY, MediaType.get("application/x-www-form-urlencoded"))) + .build(); + try (Response response = httpClient.newCall(request).execute()) { + assertEquals(200, response.code()); + } + + TEST_WRITER.waitForTraces(1); + List> traces = TEST_WRITER.getTraces(); + assertEquals(1, traces.size()); + List spans = traces.get(0); + assertEquals(1, spans.size()); + SpanData spanData = spans.get(0); + assertEquals( + "\"" + new Struts2Action().getJsonString() + "\"", + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + assertEquals( + REQUEST_BODY, spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_REQUEST_BODY)); + } + + @Test + public void getHeaders() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/context/headers", serverPort)) + .get() + .header(REQUEST_HEADER, REQUEST_HEADER_VALUE) + .build(); + try (Response response = httpClient.newCall(request).execute()) { + assertEquals(200, response.code()); + } + + TEST_WRITER.waitForTraces(1); + List> traces = TEST_WRITER.getTraces(); + assertEquals(1, traces.size()); + List spans = traces.get(0); + assertEquals(1, spans.size()); + SpanData spanData = spans.get(0); + assertEquals( + RESPONSE_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpResponseHeader(RESPONSE_HEADER))); + assertEquals( + REQUEST_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpRequestHeader(REQUEST_HEADER))); + } + + @Test + public void block() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/context/body", serverPort)) + .get() + .header("mockblock", "true") + .build(); + try (Response response = httpClient.newCall(request).execute()) { + Assertions.assertEquals(403, response.code()); + } + + TEST_WRITER.waitForTraces(1); + List> traces = TEST_WRITER.getTraces(); + Assertions.assertEquals(1, traces.size()); + List spans = traces.get(0); + Assertions.assertEquals(1, spans.size()); + SpanData spanData = spans.get(0); + Assertions.assertNull( + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpResponseHeader(RESPONSE_HEADER))); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_REQUEST_BODY)); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + } +} diff --git a/instrumentation/struts-2.3/src/test/resources/struts.xml b/instrumentation/struts-2.3/src/test/resources/struts.xml new file mode 100644 index 000000000..45385f54e --- /dev/null +++ b/instrumentation/struts-2.3/src/test/resources/struts.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + headerValue + + + + + + + + + true + true + jsonString + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 56a32c81d..38dd36f31 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,3 +59,5 @@ include("instrumentation:servlet:servlet-3.0-no-wrapping") findProject(":instrumentation:servlet:servlet-3.0-no-wrapping")?.name = "servlet-3.0-no-wrapping" include("instrumentation:servlet:servlet-rw") findProject(":instrumentation:servlet:servlet-rw")?.name = "servlet-rw" +include("instrumentation:struts-2.3") +findProject(":instrumentation:struts-2.3")?.name = "struts-2.3"