Browse Source

Add instrumentation for jaxws metro 3.0+ (#9705)

Phil Clay 1 year ago
parent
commit
dc975b7bc5
27 changed files with 569 additions and 81 deletions
  1. 1 1
      docs/supported-libraries.md
  2. 0 4
      instrumentation/jaxws/jaxws-2.0-common-testing/build.gradle.kts
  3. 5 19
      instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/build.gradle.kts
  4. 0 0
      instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/groovy/MetroJaxWsTest.groovy
  5. 0 0
      instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/sun-jaxws.xml
  6. 0 0
      instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/web.xml
  7. 0 53
      instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroServerSpanNaming.java
  8. 24 0
      instrumentation/jaxws/jaxws-3.0-common-testing/build.gradle.kts
  9. 187 0
      instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/AbstractJaxWsTest.groovy
  10. 16 0
      instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/BaseHelloService.groovy
  11. 24 0
      instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/HelloService.groovy
  12. 19 0
      instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/HelloServiceImpl.groovy
  13. 35 0
      instrumentation/jaxws/jaxws-3.0-common-testing/src/main/schema/hello.xsd
  14. 27 0
      instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/build.gradle.kts
  15. 7 0
      instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/groovy/MetroJaxWsTest.groovy
  16. 6 0
      instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/sun-jaxws.xml
  17. 21 0
      instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/web.xml
  18. 26 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/build.gradle.kts
  19. 4 1
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroHelper.java
  20. 1 1
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroInstrumentationModule.java
  21. 0 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroRequest.java
  22. 161 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroServerSpanNameUpdater.java
  23. 1 1
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroSingletons.java
  24. 0 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/ServerTubeAssemblerContextInstrumentation.java
  25. 0 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/SoapFaultBuilderInstrumentation.java
  26. 0 0
      instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/TracingTube.java
  27. 4 1
      settings.gradle.kts

+ 1 - 1
docs/supported-libraries.md

@@ -51,7 +51,7 @@ These are the supported libraries and frameworks:
 | [Eclipse Grizzly](https://javaee.github.io/grizzly/httpserverframework.html)                                                                | 2.3+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | [HTTP Server Spans], [HTTP Server Metrics]                                             |
 | [Eclipse Jersey](https://eclipse-ee4j.github.io/jersey/)                                                                                    | 2.0+ (not including 3.x yet)  | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |
 | [Eclipse Jetty HTTP Client](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/client/HttpClient.html)                         | 9.2+ (not including 10+ yet)  | [opentelemetry-jetty-httpclient-9.2](../instrumentation/jetty-httpclient/jetty-httpclient-9.2/library)                                                                                                                                                                                                                                                                                  | [HTTP Client Spans], [HTTP Client Metrics]                                             |
-| [Eclipse Metro](https://projects.eclipse.org/projects/ee4j.metro)                                                                           | 2.2+ (not including 3.x yet)  | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |
+| [Eclipse Metro](https://projects.eclipse.org/projects/ee4j.metro)                                                                           | 2.2+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |
 | [Eclipse Mojarra](https://projects.eclipse.org/projects/ee4j.mojarra)                                                                       | 1.2+ (not including 3.x yet)  | N/A                                                                                                                                                                                                                                                                                                                                                                                     | Provides `http.route` [2], Controller Spans [3]                                        |
 | [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html)                         | 7.16+ and 8.0+                | N/A                                                                                                                                                                                                                                                                                                                                                                                     | [Elasticsearch Client Spans]                                                           |
 | [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html)                              | 5.0+                          | N/A                                                                                                                                                                                                                                                                                                                                                                                     | [Database Client Spans]                                                                |

+ 0 - 4
instrumentation/jaxws/jaxws-2.0-common-testing/build.gradle.kts

@@ -14,10 +14,6 @@ dependencies {
   api("javax.xml.ws:jaxws-api:2.0")
   api("javax.jws:javax.jws-api:1.1")
 
-  api("ch.qos.logback:logback-classic")
-  api("org.slf4j:log4j-over-slf4j")
-  api("org.slf4j:jcl-over-slf4j")
-  api("org.slf4j:jul-to-slf4j")
   api("org.eclipse.jetty:jetty-webapp:9.4.35.v20201120")
   api("org.springframework.ws:spring-ws-core:3.0.0.RELEASE")
 

+ 5 - 19
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/build.gradle.kts → instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/build.gradle.kts

@@ -1,31 +1,17 @@
 plugins {
-  id("otel.javaagent-instrumentation")
-}
-
-muzzle {
-  pass {
-    group.set("com.sun.xml.ws")
-    module.set("jaxws-rt")
-    versions.set("[2.2.0.1,3)")
-    // version 2.3.4 depends on org.glassfish.gmbal:gmbal-api-only:4.0.3 which does not exist
-    skip("2.3.4")
-    assertInverse.set(true)
-    extraDependency("javax.servlet:javax.servlet-api:3.0.1")
-  }
+  id("otel.javaagent-testing")
 }
 
 dependencies {
-  bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap"))
-
-  library("com.sun.xml.ws:jaxws-rt:2.2.0.1")
+  testLibrary("com.sun.xml.ws:jaxws-rt:2.2.0.1")
   // early versions of streambuffer depend on latest release of org.jvnet.staxex:stax-ex
   // which doesn't work with java 8
-  library("com.sun.xml.stream.buffer:streambuffer:1.4")
-
-  compileOnly("javax.servlet:javax.servlet-api:3.0.1")
+  testLibrary("com.sun.xml.stream.buffer:streambuffer:1.4")
 
+  testImplementation("javax.servlet:javax.servlet-api:3.0.1")
   testImplementation(project(":instrumentation:jaxws:jaxws-2.0-common-testing"))
 
+  testInstrumentation(project(":instrumentation:jaxws:jaxws-metro-2.2:javaagent"))
   testInstrumentation(project(":instrumentation:jaxws:jaxws-2.0:javaagent"))
   testInstrumentation(project(":instrumentation:jaxws:jaxws-jws-api-1.1:javaagent"))
 

+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/test/groovy/MetroJaxWsTest.groovy → instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/groovy/MetroJaxWsTest.groovy


+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/test/resources/test-app/WEB-INF/sun-jaxws.xml → instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/sun-jaxws.xml


+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/test/resources/test-app/WEB-INF/web.xml → instrumentation/jaxws/jaxws-2.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/web.xml


+ 0 - 53
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroServerSpanNaming.java

@@ -1,53 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.metro;
-
-import com.sun.xml.ws.api.message.Packet;
-import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.context.Context;
-import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
-import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
-import javax.servlet.http.HttpServletRequest;
-import javax.xml.ws.handler.MessageContext;
-
-public final class MetroServerSpanNaming {
-
-  public static void updateServerSpanName(Context context, MetroRequest metroRequest) {
-    String spanName = metroRequest.spanName();
-    if (spanName == null) {
-      return;
-    }
-
-    Span serverSpan = LocalRootSpan.fromContextOrNull(context);
-    if (serverSpan == null) {
-      return;
-    }
-
-    Packet packet = metroRequest.packet();
-    if (packet.supports(MessageContext.SERVLET_REQUEST)) {
-      Object request = packet.get(MessageContext.SERVLET_REQUEST);
-      if (request instanceof HttpServletRequest) {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        String servletPath = httpRequest.getServletPath();
-        if (!servletPath.isEmpty()) {
-          String pathInfo = httpRequest.getPathInfo();
-          if (pathInfo != null) {
-            spanName = servletPath + "/" + spanName;
-          } else {
-            // when pathInfo is null then there is a servlet that is mapped to this exact service
-            // servletPath already contains the service name
-            String operationName = packet.getWSDLOperation().getLocalPart();
-            spanName = servletPath + "/" + operationName;
-          }
-        }
-      }
-    }
-
-    serverSpan.updateName(ServletContextPath.prepend(context, spanName));
-  }
-
-  private MetroServerSpanNaming() {}
-}

+ 24 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/build.gradle.kts

@@ -0,0 +1,24 @@
+plugins {
+  id("org.unbroken-dome.xjc")
+  id("otel.java-conventions")
+}
+
+tasks {
+  named<Checkstyle>("checkstyleMain") {
+    // exclude generated web service classes
+    exclude("**/hello_web_service/**")
+  }
+}
+
+dependencies {
+  api("jakarta.xml.ws:jakarta.xml.ws-api:3.0.0")
+  api("jakarta.jws:jakarta.jws-api:3.0.0")
+
+  api("org.eclipse.jetty:jetty-webapp:11.0.17")
+  api("org.springframework.ws:spring-ws-core:4.0.0")
+
+  implementation(project(":testing-common"))
+
+  xjcTool("com.sun.xml.bind:jaxb-xjc:3.0.2")
+  xjcTool("com.sun.xml.bind:jaxb-impl:3.0.2")
+}

+ 187 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/AbstractJaxWsTest.groovy

@@ -0,0 +1,187 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
+import io.opentelemetry.instrumentation.test.asserts.TraceAssert
+import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait
+import io.opentelemetry.sdk.trace.data.SpanData
+import io.opentelemetry.semconv.SemanticAttributes
+import io.opentelemetry.test.hello_web_service.Hello2Request
+import io.opentelemetry.test.hello_web_service.HelloRequest
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.util.resource.Resource
+import org.eclipse.jetty.webapp.WebAppContext
+import org.springframework.oxm.jaxb.Jaxb2Marshaller
+import org.springframework.util.ClassUtils
+import org.springframework.ws.client.core.WebServiceTemplate
+import org.springframework.ws.soap.client.SoapFaultClientException
+import spock.lang.Shared
+import spock.lang.Unroll
+
+import static io.opentelemetry.api.trace.SpanKind.INTERNAL
+import static io.opentelemetry.api.trace.SpanKind.SERVER
+import static io.opentelemetry.api.trace.StatusCode.ERROR
+
+abstract class AbstractJaxWsTest extends AgentInstrumentationSpecification implements HttpServerTestTrait<Server> {
+
+  @Shared
+  private Jaxb2Marshaller marshaller = new Jaxb2Marshaller()
+
+  @Shared
+  protected WebServiceTemplate webServiceTemplate = new WebServiceTemplate(marshaller)
+
+  def setupSpec() {
+    setupServer()
+
+    marshaller.setPackagesToScan(ClassUtils.getPackageName(HelloRequest))
+    marshaller.afterPropertiesSet()
+  }
+
+  def cleanupSpec() {
+    cleanupServer()
+  }
+
+  @Override
+  Server startServer(int port) {
+    WebAppContext webAppContext = new WebAppContext()
+    webAppContext.setContextPath(getContextPath())
+    // set up test application
+    webAppContext.setBaseResource(Resource.newSystemResource("test-app"))
+    webAppContext.getMetaData().addWebInfResource(Resource.newClassPathResource("/"))
+
+    def jettyServer = new Server(port)
+    jettyServer.connectors.each {
+      it.setHost('localhost')
+    }
+
+    jettyServer.setHandler(webAppContext)
+    jettyServer.start()
+
+    return jettyServer
+  }
+
+  @Override
+  void stopServer(Server server) {
+    server.stop()
+    server.destroy()
+  }
+
+  @Override
+  String getContextPath() {
+    return "/jetty-context"
+  }
+
+  String getServiceAddress(String serviceName) {
+    return address.resolve("ws/" + serviceName).toString()
+  }
+
+  def makeRequest(methodName, name) {
+    Object request = null
+    if ("hello" == methodName) {
+      request = new HelloRequest(name: name)
+    } else if ("hello2" == methodName) {
+      request = new Hello2Request(name: name)
+    } else {
+      throw new IllegalArgumentException(methodName)
+    }
+
+    return webServiceTemplate.marshalSendAndReceive(getServiceAddress("HelloService"), request)
+  }
+
+  @Unroll
+  def "test #methodName"() {
+    setup:
+    def response = makeRequest(methodName, "Test")
+
+    expect:
+    response.getMessage() == "Hello Test"
+
+    and:
+    def spanCount = 2
+    assertTraces(1) {
+      trace(0, spanCount) {
+        serverSpan(it, 0, serverSpanName(methodName))
+        handlerSpan(it, 1, methodName, span(0))
+      }
+    }
+
+    where:
+    methodName << ["hello", "hello2"]
+  }
+
+  @Unroll
+  def "test #methodName exception"() {
+    when:
+    makeRequest(methodName, "exception")
+
+    then:
+    def error = thrown(SoapFaultClientException)
+    error.getMessage() == "hello exception"
+
+    and:
+    def spanCount = 2
+    def expectedException = new Exception("hello exception")
+    assertTraces(1) {
+      trace(0, spanCount) {
+        serverSpan(it, 0, serverSpanName(methodName), expectedException)
+        handlerSpan(it, 1, methodName, span(0), expectedException)
+      }
+    }
+
+    where:
+    methodName << ["hello", "hello2"]
+  }
+
+  def serverSpanName(String operation) {
+    return getContextPath() + "/ws/HelloService/" + operation
+  }
+
+  static serverSpan(TraceAssert trace, int index, String operation, Throwable exception = null) {
+    trace.span(index) {
+      hasNoParent()
+      name operation
+      kind SERVER
+      if (exception != null) {
+        status ERROR
+      }
+    }
+  }
+
+  static handlerSpan(TraceAssert trace, int index, String operation, Object parentSpan = null, Throwable exception = null) {
+    trace.span(index) {
+      if (parentSpan == null) {
+        hasNoParent()
+      } else {
+        childOf((SpanData) parentSpan)
+      }
+      name "HelloService/" + operation
+      kind INTERNAL
+      if (exception) {
+        status ERROR
+        errorEvent(exception.class, exception.message)
+      }
+    }
+  }
+
+  static annotationHandlerSpan(TraceAssert trace, int index, String methodName, Object parentSpan = null, Throwable exception = null) {
+    trace.span(index) {
+      if (parentSpan == null) {
+        hasNoParent()
+      } else {
+        childOf((SpanData) parentSpan)
+      }
+      name "HelloServiceImpl." + methodName
+      kind INTERNAL
+      if (exception) {
+        status ERROR
+        errorEvent(exception.class, exception.message)
+      }
+      attributes {
+        "$SemanticAttributes.CODE_NAMESPACE" "hello.HelloServiceImpl"
+        "$SemanticAttributes.CODE_FUNCTION" methodName
+      }
+    }
+  }
+}

+ 16 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/BaseHelloService.groovy

@@ -0,0 +1,16 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package hello
+
+class BaseHelloService {
+
+  String hello2(String name) {
+    if ("exception" == name) {
+      throw new Exception("hello exception")
+    }
+    return "Hello " + name
+  }
+}

+ 24 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/HelloService.groovy

@@ -0,0 +1,24 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package hello
+
+import jakarta.jws.WebParam
+import jakarta.jws.WebResult
+import jakarta.jws.WebService
+import jakarta.xml.ws.RequestWrapper
+
+@WebService(targetNamespace = "http://opentelemetry.io/test/hello-web-service")
+interface HelloService {
+
+  @RequestWrapper(localName = "helloRequest")
+  @WebResult(name = "message")
+  String hello(@WebParam(name = "name") String name)
+
+  @RequestWrapper(localName = "hello2Request")
+  @WebResult(name = "message")
+  String hello2(@WebParam(name = "name") String name)
+
+}

+ 19 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/src/main/groovy/hello/HelloServiceImpl.groovy

@@ -0,0 +1,19 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package hello
+
+import jakarta.jws.WebService
+
+@WebService(serviceName = "HelloService", endpointInterface = "hello.HelloService", targetNamespace = "http://opentelemetry.io/test/hello-web-service")
+class HelloServiceImpl extends BaseHelloService implements HelloService {
+
+  String hello(String name) {
+    if ("exception" == name) {
+      throw new Exception("hello exception")
+    }
+    return "Hello " + name
+  }
+}

+ 35 - 0
instrumentation/jaxws/jaxws-3.0-common-testing/src/main/schema/hello.xsd

@@ -0,0 +1,35 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  targetNamespace="http://opentelemetry.io/test/hello-web-service" elementFormDefault="qualified">
+
+  <xs:element name="helloRequest">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="name" type="xs:string" form="unqualified"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="helloResponse">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="message" type="xs:string" form="unqualified"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="hello2Request">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="name" type="xs:string" form="unqualified"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="hello2Response">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="message" type="xs:string" form="unqualified"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>

+ 27 - 0
instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/build.gradle.kts

@@ -0,0 +1,27 @@
+plugins {
+  id("otel.javaagent-testing")
+}
+
+dependencies {
+  testLibrary("com.sun.xml.ws:jaxws-rt:3.0.0")
+
+  testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0")
+  testImplementation(project(":instrumentation:jaxws:jaxws-3.0-common-testing"))
+
+  testInstrumentation(project(":instrumentation:jaxws:jaxws-metro-2.2:javaagent"))
+
+  testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent"))
+  testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
+}
+
+otelJava {
+  minJavaVersionSupported.set(JavaVersion.VERSION_17)
+}
+
+tasks.withType<Test>().configureEach {
+  // required on jdk17
+  jvmArgs("--add-exports=java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED")
+  jvmArgs("--add-exports=java.xml/com.sun.org.apache.xerces.internal.jaxp=ALL-UNNAMED")
+  jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
+  jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
+}

+ 7 - 0
instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/groovy/MetroJaxWsTest.groovy

@@ -0,0 +1,7 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+class MetroJaxWsTest extends AbstractJaxWsTest {
+}

+ 6 - 0
instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/sun-jaxws.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
+  version="2.0">
+  <endpoint name="Hello" implementation="hello.HelloServiceImpl"
+    url-pattern="/ws/HelloService" />
+</endpoints>

+ 21 - 0
instrumentation/jaxws/jaxws-3.0-metro-2.2-testing/src/test/resources/test-app/WEB-INF/web.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="3.0" metadata-complete="false"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns="http://java.sun.com/xml/ns/javaee"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+
+  <listener>
+    <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
+  </listener>
+
+  <servlet>
+    <servlet-name>WSServlet</servlet-name>
+    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>WSServlet</servlet-name>
+    <url-pattern>/ws/*</url-pattern>
+  </servlet-mapping>
+</web-app>

+ 26 - 0
instrumentation/jaxws/jaxws-metro-2.2/javaagent/build.gradle.kts

@@ -0,0 +1,26 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+  pass {
+    group.set("com.sun.xml.ws")
+    module.set("jaxws-rt")
+    versions.set("[2.2.0.1,)")
+    // version 2.3.4 depends on org.glassfish.gmbal:gmbal-api-only:4.0.3 which does not exist
+    skip("2.3.4")
+    assertInverse.set(true)
+  }
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap"))
+
+  library("com.sun.xml.ws:jaxws-rt:2.2.0.1")
+  // early versions of streambuffer depend on latest release of org.jvnet.staxex:stax-ex
+  // which doesn't work with java 8
+  library("com.sun.xml.stream.buffer:streambuffer:1.4")
+
+  compileOnly("javax.xml.ws:jaxws-api:2.0")
+  compileOnly("jakarta.xml.ws:jakarta.xml.ws-api:3.0.0")
+}

+ 4 - 1
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroHelper.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroHelper.java

@@ -18,13 +18,16 @@ public final class MetroHelper {
   private static final String SCOPE_KEY = MetroHelper.class.getName() + ".Scope";
   private static final String THROWABLE_KEY = MetroHelper.class.getName() + ".Throwable";
 
+  private static final MetroServerSpanNameUpdater SPAN_NAME_UPDATER =
+      new MetroServerSpanNameUpdater();
+
   private MetroHelper() {}
 
   public static void start(WSEndpoint<?> endpoint, Packet packet) {
     Context parentContext = Context.current();
 
     MetroRequest request = new MetroRequest(endpoint, packet);
-    MetroServerSpanNaming.updateServerSpanName(parentContext, request);
+    SPAN_NAME_UPDATER.updateServerSpanName(parentContext, request);
 
     if (!instrumenter().shouldStart(parentContext, request)) {
       return;

+ 1 - 1
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroInstrumentationModule.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroInstrumentationModule.java

@@ -22,7 +22,7 @@ public class MetroInstrumentationModule extends InstrumentationModule {
 
   @Override
   public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
-    return hasClassesNamed("javax.jws.WebService");
+    return hasClassesNamed("com.sun.xml.ws.api.pipe.ServerTubeAssemblerContext");
   }
 
   @Override

+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroRequest.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroRequest.java


+ 161 - 0
instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroServerSpanNameUpdater.java

@@ -0,0 +1,161 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.metro;
+
+import com.sun.xml.ws.api.message.Packet;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
+import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+final class MetroServerSpanNameUpdater {
+
+  private static final Logger logger = Logger.getLogger(MetroServerSpanNameUpdater.class.getName());
+
+  /**
+   * Map of message context key names to the {@link HttpServletRequestAdapter} to handle the {@code
+   * HttpServletRequest} found at that message context key.
+   *
+   * <p>This map will contain at most two entries:
+   *
+   * <ul>
+   *   <li>{@value javax.xml.ws.handler.MessageContext#SERVLET_REQUEST} to an {@link
+   *       HttpServletRequestAdapter} that handles {@code javax.servlet.http.HttpServletRequest}
+   *   <li>{@value jakarta.xml.ws.handler.MessageContext#SERVLET_REQUEST} to an {@link
+   *       HttpServletRequestAdapter} that handles {@code jakarta.servlet.http.HttpServletRequest}
+   * </ul>
+   */
+  private final Map<String, HttpServletRequestAdapter> servletRequestAdapters;
+
+  public MetroServerSpanNameUpdater() {
+    this.servletRequestAdapters = new LinkedHashMap<>();
+
+    registerHttpServletRequestAdapter(
+        "Jakarta EE",
+        // Same as jakarta.xml.ws.handler.MessageContext.SERVLET_REQUEST
+        "jakarta.xml.ws.servlet.request",
+        "jakarta.servlet.http.HttpServletRequest");
+
+    registerHttpServletRequestAdapter(
+        "Java EE",
+        // Same as javax.xml.ws.handler.MessageContext.SERVLET_REQUEST
+        "javax.xml.ws.servlet.request",
+        "javax.servlet.http.HttpServletRequest");
+  }
+
+  /**
+   * Registers a {@link HttpServletRequestAdapter} in the {@link #servletRequestAdapters} with the
+   * given {@code key} if the given {@code httpServletRequestClassName} is on the classpath.
+   */
+  private void registerHttpServletRequestAdapter(
+      String name, String key, String httpServletRequestClassName) {
+    HttpServletRequestAdapter adapter;
+    try {
+      adapter = new HttpServletRequestAdapter(Class.forName(httpServletRequestClassName));
+    } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
+      // Ignore.  Don't register
+      return;
+    }
+    servletRequestAdapters.put(key, adapter);
+    logger.finest(() -> "Enabled " + name + " jaxws metro server span naming");
+  }
+
+  public void updateServerSpanName(Context context, MetroRequest metroRequest) {
+    String spanName = metroRequest.spanName();
+    if (spanName == null) {
+      return;
+    }
+
+    Span serverSpan = LocalRootSpan.fromContextOrNull(context);
+    if (serverSpan == null) {
+      return;
+    }
+
+    for (Map.Entry<String, HttpServletRequestAdapter> httpServletRequestAdapterEntry :
+        servletRequestAdapters.entrySet()) {
+      Packet packet = metroRequest.packet();
+      String key = httpServletRequestAdapterEntry.getKey();
+      if (packet.supports(key)) {
+        Object request = packet.get(key);
+        HttpServletRequestAdapter httpServletRequestAdapter =
+            httpServletRequestAdapterEntry.getValue();
+        if (httpServletRequestAdapter.canHandle(request)) {
+          String servletPath = httpServletRequestAdapter.getServletPath(request);
+          if (!servletPath.isEmpty()) {
+            String pathInfo = httpServletRequestAdapter.getPathInfo(request);
+            if (pathInfo != null) {
+              spanName = servletPath + "/" + spanName;
+            } else {
+              // when pathInfo is null then there is a servlet that is mapped to this exact service
+              // servletPath already contains the service name
+              String operationName = packet.getWSDLOperation().getLocalPart();
+              spanName = servletPath + "/" + operationName;
+            }
+            break;
+          }
+        }
+      }
+    }
+
+    serverSpan.updateName(ServletContextPath.prepend(context, spanName));
+  }
+
+  /**
+   * Adapter class for accessing the methods needed from either {@code
+   * jakarta.servlet.http.HttpServletRequest} or {@code javax.servlet.http.HttpServletRequest}.
+   */
+  private static class HttpServletRequestAdapter {
+
+    private final Class<?> httpServletRequestClass;
+    private final MethodHandle getServletPathMethodHandle;
+    private final MethodHandle getPathInfoMethodHandle;
+
+    private HttpServletRequestAdapter(Class<?> httpServletRequestClass)
+        throws NoSuchMethodException, IllegalAccessException {
+      this.httpServletRequestClass =
+          Objects.requireNonNull(
+              httpServletRequestClass, "httpServletRequestClass must not be null");
+
+      MethodHandles.Lookup lookup = MethodHandles.lookup();
+      this.getServletPathMethodHandle =
+          lookup.unreflect(httpServletRequestClass.getMethod("getServletPath"));
+      this.getPathInfoMethodHandle =
+          lookup.unreflect(httpServletRequestClass.getMethod("getPathInfo"));
+    }
+
+    public boolean canHandle(Object httpServletRequest) {
+      return httpServletRequestClass.isInstance(httpServletRequest);
+    }
+
+    public String getServletPath(Object httpServletRequest) {
+      return invokeSafely(getServletPathMethodHandle, httpServletRequest);
+    }
+
+    public String getPathInfo(Object httpServletRequest) {
+      return invokeSafely(getPathInfoMethodHandle, httpServletRequest);
+    }
+
+    private static String invokeSafely(MethodHandle methodHandle, Object httpServletRequest) {
+      try {
+        return (String) methodHandle.invoke(httpServletRequest);
+      } catch (RuntimeException | Error e) {
+        throw e;
+      } catch (Throwable t) {
+        /*
+         * This is impossible, because the methods being invoked do not throw checked exceptions,
+         * and unchecked exceptions and errors are handled above
+         */
+        throw new AssertionError(t);
+      }
+    }
+  }
+}

+ 1 - 1
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroSingletons.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/MetroSingletons.java

@@ -10,7 +10,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
 import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
 
 public class MetroSingletons {
-  private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jaxws-2.0-metro-2.2";
+  private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jaxws-metro-2.2";
 
   private static final Instrumenter<MetroRequest, Void> INSTRUMENTER;
 

+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/ServerTubeAssemblerContextInstrumentation.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/ServerTubeAssemblerContextInstrumentation.java


+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/SoapFaultBuilderInstrumentation.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/SoapFaultBuilderInstrumentation.java


+ 0 - 0
instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/TracingTube.java → instrumentation/jaxws/jaxws-metro-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/metro/TracingTube.java


+ 4 - 1
settings.gradle.kts

@@ -287,15 +287,18 @@ include(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-jersey-3.0:javaagent")
 include(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-resteasy-6.0:javaagent")
 include(":instrumentation:jaxrs-client:jaxrs-client-1.1-testing")
 include(":instrumentation:jaxrs-client:jaxrs-client-2.0-testing")
+include(":instrumentation:jaxws:jaxws-metro-2.2:javaagent")
 include(":instrumentation:jaxws:jaxws-2.0:javaagent")
 include(":instrumentation:jaxws:jaxws-2.0-arquillian-testing")
 include(":instrumentation:jaxws:jaxws-2.0-axis2-1.6:javaagent")
 include(":instrumentation:jaxws:jaxws-2.0-cxf-3.0:javaagent")
 include(":instrumentation:jaxws:jaxws-2.0-cxf-3.0:javaagent-unit-tests")
-include(":instrumentation:jaxws:jaxws-2.0-metro-2.2:javaagent")
+include(":instrumentation:jaxws:jaxws-2.0-metro-2.2-testing")
 include(":instrumentation:jaxws:jaxws-2.0-common-testing")
 include(":instrumentation:jaxws:jaxws-2.0-tomee-testing")
 include(":instrumentation:jaxws:jaxws-2.0-wildfly-testing")
+include(":instrumentation:jaxws:jaxws-3.0-common-testing")
+include(":instrumentation:jaxws:jaxws-3.0-metro-2.2-testing")
 include(":instrumentation:jaxws:jaxws-common:javaagent")
 include(":instrumentation:jaxws:jaxws-jws-api-1.1:javaagent")
 include(":instrumentation:jboss-logmanager:jboss-logmanager-appender-1.1:javaagent")