Browse Source

Add instrumentation for JAX-RS 3.0 (#6136)

* Add instrumentation for JAX-RS 3.0

* set min java version to 11 for jax-rs 3.0

* exclude broken version

* fix muzzle range

* include correct api

* fix muzzle

* fix muzzle

* remove generics

* share test code
Lauri Tulmin 2 years ago
parent
commit
cd2e11a9d6
88 changed files with 3311 additions and 717 deletions
  1. 1 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ContainerRequestFilterInstrumentation.java
  2. 5 3
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/DefaultRequestContextInstrumentation.java
  3. 6 3
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAnnotationsInstrumentation.java
  4. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAnnotationsSingletons.java
  5. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAsyncResponseInstrumentation.java
  6. 1 3
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/build.gradle.kts
  7. 0 161
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/HandlerData.java
  8. 60 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Jaxrs2HandlerData.java
  9. 30 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Jaxrs2RequestContextHelper.java
  10. 0 15
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsConstants.java
  11. 1 14
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/build.gradle.kts
  12. 3 130
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/src/main/groovy/JaxRsFilterTest.groovy
  13. 4 316
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/src/main/groovy/JaxRsHttpServerTest.groovy
  14. 5 4
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfRequestContextInstrumentation.java
  15. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfSingletons.java
  16. 1 1
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfSpanName.java
  17. 18 10
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts
  18. 6 4
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseyRequestContextInstrumentation.java
  19. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseySingletons.java
  20. 1 1
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseySpanName.java
  21. 69 19
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyFilterTest.groovy
  22. 14 3
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts
  23. 6 4
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30RequestContextInstrumentation.java
  24. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30Singletons.java
  25. 14 3
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts
  26. 6 4
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31RequestContextInstrumentation.java
  27. 2 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31Singletons.java
  28. 1 0
      instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyRootNodeTypeInstrumentation.java
  29. 22 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/build.gradle.kts
  30. 60 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ContainerRequestFilterInstrumentation.java
  31. 95 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/DefaultRequestContextInstrumentation.java
  32. 171 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsInstrumentation.java
  33. 37 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsInstrumentationModule.java
  34. 22 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsSingletons.java
  35. 107 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAsyncResponseInstrumentation.java
  36. 162 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/test/groovy/JaxrsAnnotationsInstrumentationTest.groovy
  37. 69 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/test/java/JavaInterfaces.java
  38. 10 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/build.gradle.kts
  39. 42 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/AbstractRequestContextInstrumentation.java
  40. 60 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/Jaxrs3HandlerData.java
  41. 28 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/Jaxrs3RequestContextHelper.java
  42. 10 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/build.gradle.kts
  43. 62 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsFilterTest.groovy
  44. 15 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsHttpServerTest.groovy
  45. 41 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsJettyHttpServerTest.groovy
  46. 239 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/test/JaxRsTestResource.groovy
  47. 53 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/java/Resource.java
  48. 41 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/build.gradle.kts
  49. 28 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyInstrumentationModule.java
  50. 81 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyRequestContextInstrumentation.java
  51. 47 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyResourceMethodDispatcherInstrumentation.java
  52. 62 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyServletContainerInstrumentation.java
  53. 22 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseySingletons.java
  54. 43 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseySpanName.java
  55. 84 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyFilterTest.groovy
  56. 45 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy
  57. 18 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy
  58. 23 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyStartupListener.groovy
  59. 10 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/webapp/WEB-INF/web.xml
  60. 43 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts
  61. 30 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyInstrumentationModule.java
  62. 75 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyRequestContextInstrumentation.java
  63. 70 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyResourceLocatorInvokerInstrumentation.java
  64. 47 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyResourceMethodInvokerInstrumentation.java
  65. 63 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyRootNodeTypeInstrumentation.java
  66. 56 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyServletContainerDispatcherInstrumentation.java
  67. 22 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasySingletons.java
  68. 37 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasySpanName.java
  69. 43 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyFilterTest.groovy
  70. 35 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyHttpServerTest.groovy
  71. 7 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyJettyHttpServerTest.groovy
  72. 24 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyStartupListener.groovy
  73. 10 0
      instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/webapp/WEB-INF/web.xml
  74. 10 0
      instrumentation/jaxrs/jaxrs-common/javaagent/build.gradle.kts
  75. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/AsyncResponseData.java
  76. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/CompletionStageFinishCallback.java
  77. 141 0
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/HandlerData.java
  78. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsCodeAttributesGetter.java
  79. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsConfig.java
  80. 15 0
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsConstants.java
  81. 2 2
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsInstrumenterFactory.java
  82. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsPathUtil.java
  83. 1 1
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsServerSpanNaming.java
  84. 3 11
      instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/RequestContextHelper.java
  85. 16 0
      instrumentation/jaxrs/jaxrs-common/testing/build.gradle.kts
  86. 147 0
      instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsFilterTest.groovy
  87. 329 0
      instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy
  88. 7 0
      settings.gradle.kts

+ 1 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ContainerRequestFilterInstrumentation.java

@@ -14,6 +14,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
 import net.bytebuddy.asm.Advice;

+ 5 - 3
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/DefaultRequestContextInstrumentation.java

@@ -12,6 +12,8 @@ import io.opentelemetry.context.Scope;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
 import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsServerSpanNaming;
 import java.lang.reflect.Method;
 import javax.ws.rs.container.ContainerRequestContext;
 import net.bytebuddy.asm.Advice;
@@ -38,7 +40,7 @@ public class DefaultRequestContextInstrumentation extends AbstractRequestContext
     @Advice.OnMethodEnter(suppress = Throwable.class)
     public static void createGenericSpan(
         @Advice.This ContainerRequestContext requestContext,
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope) {
       if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null) {
@@ -60,7 +62,7 @@ public class DefaultRequestContextInstrumentation extends AbstractRequestContext
       }
 
       Context parentContext = Java8BytecodeBridge.currentContext();
-      handlerData = new HandlerData(filterClass, method);
+      handlerData = new Jaxrs2HandlerData(filterClass, method);
 
       HttpRouteHolder.updateHttpRoute(
           parentContext,
@@ -78,7 +80,7 @@ public class DefaultRequestContextInstrumentation extends AbstractRequestContext
 
     @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
     public static void stopSpan(
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope,
         @Advice.Thrown Throwable throwable) {

+ 6 - 3
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAnnotationsInstrumentation.java

@@ -24,6 +24,9 @@ import io.opentelemetry.javaagent.bootstrap.CallDepth;
 import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.AsyncResponseData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.CompletionStageFinishCallback;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsServerSpanNaming;
 import java.lang.reflect.Method;
 import java.util.concurrent.CompletionStage;
 import javax.ws.rs.Path;
@@ -74,7 +77,7 @@ public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
         @Advice.Origin Method method,
         @Advice.AllArguments Object[] args,
         @Advice.Local("otelCallDepth") CallDepth callDepth,
-        @Advice.Local("otelHandlerData") HandlerData handlerData,
+        @Advice.Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Advice.Local("otelContext") Context context,
         @Advice.Local("otelScope") Scope scope,
         @Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {
@@ -102,7 +105,7 @@ public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
       }
 
       Context parentContext = Java8BytecodeBridge.currentContext();
-      handlerData = new HandlerData(target.getClass(), method);
+      handlerData = new Jaxrs2HandlerData(target.getClass(), method);
 
       HttpRouteHolder.updateHttpRoute(
           parentContext,
@@ -127,7 +130,7 @@ public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
         @Advice.Return(readOnly = false, typing = Typing.DYNAMIC) Object returnValue,
         @Advice.Thrown Throwable throwable,
         @Advice.Local("otelCallDepth") CallDepth callDepth,
-        @Advice.Local("otelHandlerData") HandlerData handlerData,
+        @Advice.Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Advice.Local("otelContext") Context context,
         @Advice.Local("otelScope") Scope scope,
         @Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAnnotationsSingletons.java

@@ -6,6 +6,8 @@
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
 
 public final class JaxrsAnnotationsSingletons {
 

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsAsyncResponseInstrumentation.java

@@ -17,6 +17,8 @@ import io.opentelemetry.instrumentation.api.util.VirtualField;
 import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.AsyncResponseData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConfig;
 import javax.ws.rs.container.AsyncResponse;
 import net.bytebuddy.asm.Advice;
 import net.bytebuddy.description.type.TypeDescription;

+ 1 - 3
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/build.gradle.kts

@@ -4,9 +4,7 @@ plugins {
 
 dependencies {
   bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+  api(project(":instrumentation:jaxrs:jaxrs-common:javaagent"))
 
   compileOnly("javax.ws.rs:javax.ws.rs-api:2.0")
-
-  compileOnly("com.google.auto.value:auto-value-annotations")
-  annotationProcessor("com.google.auto.value:auto-value")
 }

+ 0 - 161
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/HandlerData.java

@@ -1,161 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
-
-import io.opentelemetry.javaagent.bootstrap.jaxrs.ClassHierarchyIterable;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.Path;
-
-public class HandlerData {
-
-  private static final ClassValue<Map<Method, String>> serverSpanNames =
-      new ClassValue<Map<Method, String>>() {
-        @Override
-        protected Map<Method, String> computeValue(Class<?> type) {
-          return new ConcurrentHashMap<>();
-        }
-      };
-
-  private final Class<?> target;
-  private final Method method;
-
-  public HandlerData(Class<?> target, Method method) {
-    this.target = target;
-    this.method = method;
-  }
-
-  public Class<?> codeClass() {
-    return target;
-  }
-
-  public String methodName() {
-    return method.getName();
-  }
-
-  /**
-   * Returns the span name given a JaxRS annotated method. Results are cached so this method can be
-   * called multiple times without significantly impacting performance.
-   *
-   * @return The result can be an empty string but will never be {@code null}.
-   */
-  String getServerSpanName() {
-    Map<Method, String> classMap = serverSpanNames.get(target);
-    String spanName = classMap.get(method);
-    if (spanName == null) {
-      String httpMethod = null;
-      Path methodPath = null;
-      Path classPath = findClassPath(target);
-      for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
-        Method currentMethod;
-        if (currentClass.equals(target)) {
-          currentMethod = method;
-        } else {
-          currentMethod = findMatchingMethod(method, currentClass.getDeclaredMethods());
-        }
-
-        if (currentMethod != null) {
-          if (httpMethod == null) {
-            httpMethod = locateHttpMethod(currentMethod);
-          }
-          if (methodPath == null) {
-            methodPath = findMethodPath(currentMethod);
-          }
-
-          if (httpMethod != null && methodPath != null) {
-            break;
-          }
-        }
-      }
-      spanName = buildSpanName(classPath, methodPath);
-      classMap.put(method, spanName);
-    }
-
-    return spanName;
-  }
-
-  private static String locateHttpMethod(Method method) {
-    String httpMethod = null;
-    for (Annotation ann : method.getDeclaredAnnotations()) {
-      if (ann.annotationType().getAnnotation(HttpMethod.class) != null) {
-        httpMethod = ann.annotationType().getSimpleName();
-      }
-    }
-    return httpMethod;
-  }
-
-  private static Path findMethodPath(Method method) {
-    return method.getAnnotation(Path.class);
-  }
-
-  private static Path findClassPath(Class<?> target) {
-    for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
-      Path annotation = currentClass.getAnnotation(Path.class);
-      if (annotation != null) {
-        // Annotation overridden, no need to continue.
-        return annotation;
-      }
-    }
-
-    return null;
-  }
-
-  private static Method findMatchingMethod(Method baseMethod, Method[] methods) {
-    nextMethod:
-    for (Method method : methods) {
-      if (!baseMethod.getReturnType().equals(method.getReturnType())) {
-        continue;
-      }
-
-      if (!baseMethod.getName().equals(method.getName())) {
-        continue;
-      }
-
-      Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
-      Class<?>[] parameterTypes = method.getParameterTypes();
-      if (baseParameterTypes.length != parameterTypes.length) {
-        continue;
-      }
-      for (int i = 0; i < baseParameterTypes.length; i++) {
-        if (!baseParameterTypes[i].equals(parameterTypes[i])) {
-          continue nextMethod;
-        }
-      }
-      return method;
-    }
-    return null;
-  }
-
-  private static String buildSpanName(Path classPath, Path methodPath) {
-    StringBuilder spanNameBuilder = new StringBuilder();
-    boolean skipSlash = false;
-    if (classPath != null) {
-      String classPathValue = classPath.value();
-      if (!classPathValue.startsWith("/")) {
-        spanNameBuilder.append("/");
-      }
-      spanNameBuilder.append(classPathValue);
-      skipSlash = classPathValue.endsWith("/") || classPathValue.isEmpty();
-    }
-
-    if (methodPath != null) {
-      String path = methodPath.value();
-      if (skipSlash) {
-        if (path.startsWith("/")) {
-          path = path.length() == 1 ? "" : path.substring(1);
-        }
-      } else if (!path.startsWith("/")) {
-        spanNameBuilder.append("/");
-      }
-      spanNameBuilder.append(path);
-    }
-
-    return spanNameBuilder.toString().trim();
-  }
-}

+ 60 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Jaxrs2HandlerData.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Path;
+
+public class Jaxrs2HandlerData extends HandlerData {
+
+  private static final ClassValue<Map<Method, String>> serverSpanNames =
+      new ClassValue<Map<Method, String>>() {
+        @Override
+        protected Map<Method, String> computeValue(Class<?> type) {
+          return new ConcurrentHashMap<>();
+        }
+      };
+
+  public Jaxrs2HandlerData(Class<?> target, Method method) {
+    super(target, method);
+  }
+
+  /**
+   * Returns the span name given a JaxRS annotated method. Results are cached so this method can be
+   * called multiple times without significantly impacting performance.
+   *
+   * @return The result can be an empty string but will never be {@code null}.
+   */
+  @Override
+  public String getServerSpanName() {
+    Map<Method, String> classMap = serverSpanNames.get(target);
+    String spanName = classMap.get(method);
+    if (spanName == null) {
+      spanName = super.getServerSpanName();
+      classMap.put(method, spanName);
+    }
+
+    return spanName;
+  }
+
+  @Override
+  protected Class<? extends Annotation> getHttpMethodAnnotation() {
+    return HttpMethod.class;
+  }
+
+  @Override
+  protected Supplier<String> getPathAnnotation(AnnotatedElement annotatedElement) {
+    Path path = annotatedElement.getAnnotation(Path.class);
+    return path != null ? path::value : null;
+  }
+}

+ 30 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Jaxrs2RequestContextHelper.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.RequestContextHelper;
+import javax.ws.rs.container.ContainerRequestContext;
+
+public final class Jaxrs2RequestContextHelper {
+  public static Context createOrUpdateAbortSpan(
+      Instrumenter<HandlerData, Void> instrumenter,
+      ContainerRequestContext requestContext,
+      HandlerData handlerData) {
+
+    if (handlerData == null) {
+      return null;
+    }
+
+    requestContext.setProperty(JaxrsConstants.ABORT_HANDLED, true);
+    return RequestContextHelper.createOrUpdateAbortSpan(instrumenter, handlerData);
+  }
+
+  private Jaxrs2RequestContextHelper() {}
+}

+ 0 - 15
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsConstants.java

@@ -1,15 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
-
-final class JaxrsConstants {
-  public static final String ABORT_FILTER_CLASS =
-      "io.opentelemetry.javaagent.instrumentation.jaxrs2.filter.abort.class";
-  public static final String ABORT_HANDLED =
-      "io.opentelemetry.javaagent.instrumentation.jaxrs2.filter.abort.handled";
-
-  private JaxrsConstants() {}
-}

+ 1 - 14
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/build.gradle.kts

@@ -3,21 +3,8 @@ plugins {
 }
 
 dependencies {
-  api(project(":testing-common"))
+  api(project(":instrumentation:jaxrs:jaxrs-common:testing"))
   api("javax.ws.rs:javax.ws.rs-api:2.0")
 
-  implementation("org.apache.groovy:groovy")
-  implementation("io.opentelemetry:opentelemetry-api")
-  implementation("org.spockframework:spock-core")
-  implementation("org.slf4j:slf4j-api")
-  implementation("ch.qos.logback:logback-classic")
-  implementation("org.slf4j:log4j-over-slf4j")
-  implementation("org.slf4j:jcl-over-slf4j")
-  implementation("org.slf4j:jul-to-slf4j")
-
-  implementation(project(":javaagent-extension-api"))
-  implementation(project(":instrumentation-api"))
-  implementation(project(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent"))
-
   compileOnly("org.eclipse.jetty:jetty-webapp:8.0.0.v20110901")
 }

+ 3 - 130
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/src/main/groovy/JaxRsFilterTest.groovy

@@ -3,9 +3,6 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import org.junit.jupiter.api.Assumptions
 import spock.lang.Shared
 import spock.lang.Unroll
 
@@ -16,12 +13,8 @@ import javax.ws.rs.core.MediaType
 import javax.ws.rs.core.Response
 import javax.ws.rs.ext.Provider
 
-import static io.opentelemetry.api.trace.SpanKind.INTERNAL
-import static io.opentelemetry.api.trace.SpanKind.SERVER
-import static io.opentelemetry.api.trace.StatusCode.UNSET
-
 @Unroll
-abstract class JaxRsFilterTest extends AgentInstrumentationSpecification {
+abstract class JaxRsFilterTest extends AbstractJaxRsFilterTest {
 
   @Shared
   SimpleRequestFilter simpleRequestFilter = new SimpleRequestFilter()
@@ -29,130 +22,10 @@ abstract class JaxRsFilterTest extends AgentInstrumentationSpecification {
   @Shared
   PrematchRequestFilter prematchRequestFilter = new PrematchRequestFilter()
 
-  abstract makeRequest(String url)
-
-  Tuple2<String, String> runRequest(String resource) {
-    if (runsOnServer()) {
-      return makeRequest(resource)
-    }
-    // start a trace because the test doesn't go through any servlet or other instrumentation.
-    return runWithHttpServerSpan("test.span") {
-      makeRequest(resource)
-    }
-  }
-
-  boolean testAbortPrematch() {
-    true
-  }
-
-  boolean runsOnServer() {
-    false
-  }
-
-  def "test #resource, #abortNormal, #abortPrematch"() {
-    Assumptions.assumeTrue(!abortPrematch || testAbortPrematch())
-
-    given:
+  @Override
+  void setAbortStatus(boolean abortNormal, boolean abortPrematch) {
     simpleRequestFilter.abort = abortNormal
     prematchRequestFilter.abort = abortPrematch
-    def abort = abortNormal || abortPrematch
-
-    when:
-
-    def (responseText, responseStatus) = runRequest(resource)
-
-    then:
-    responseText == expectedResponse
-
-    if (abort) {
-      responseStatus == Response.Status.UNAUTHORIZED.statusCode
-    } else {
-      responseStatus == Response.Status.OK.statusCode
-    }
-
-    assertTraces(1) {
-      trace(0, 2) {
-        span(0) {
-          name parentSpanName != null ? parentSpanName : "test.span"
-          kind SERVER
-          if (runsOnServer() && abortNormal) {
-            status UNSET
-          }
-        }
-        span(1) {
-          childOf span(0)
-          name controllerName
-          if (abortPrematch) {
-            attributes {
-              "$SemanticAttributes.CODE_NAMESPACE" "JaxRsFilterTest\$PrematchRequestFilter"
-              "$SemanticAttributes.CODE_FUNCTION" "filter"
-            }
-          } else {
-            attributes {
-              "$SemanticAttributes.CODE_NAMESPACE" ~/Resource[$]Test*/
-              "$SemanticAttributes.CODE_FUNCTION" "hello"
-            }
-          }
-        }
-      }
-    }
-
-    where:
-    resource           | abortNormal | abortPrematch | parentSpanName        | controllerName                 | expectedResponse
-    "/test/hello/bob"  | false       | false         | "/test/hello/{name}"  | "Test1.hello"                  | "Test1 bob!"
-    "/test2/hello/bob" | false       | false         | "/test2/hello/{name}" | "Test2.hello"                  | "Test2 bob!"
-    "/test3/hi/bob"    | false       | false         | "/test3/hi/{name}"    | "Test3.hello"                  | "Test3 bob!"
-
-    // Resteasy and Jersey give different resource class names for just the below case
-    // Resteasy returns "SubResource.class"
-    // Jersey returns "Test1.class
-    // "/test/hello/bob"  | true        | false         | "/test/hello/{name}"  | "Test1.hello"                  | "Aborted"
-
-    "/test2/hello/bob" | true        | false         | "/test2/hello/{name}" | "Test2.hello"                  | "Aborted"
-    "/test3/hi/bob"    | true        | false         | "/test3/hi/{name}"    | "Test3.hello"                  | "Aborted"
-    "/test/hello/bob"  | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
-    "/test2/hello/bob" | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
-    "/test3/hi/bob"    | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
-  }
-
-  def "test nested call"() {
-    given:
-    simpleRequestFilter.abort = false
-    prematchRequestFilter.abort = false
-
-    when:
-    def (responseText, responseStatus) = runRequest(resource)
-
-    then:
-    responseStatus == Response.Status.OK.statusCode
-    responseText == expectedResponse
-
-    assertTraces(1) {
-      trace(0, 2) {
-        span(0) {
-          name parentResourceName
-          kind SERVER
-          if (!runsOnServer()) {
-            attributes {
-              "$SemanticAttributes.HTTP_ROUTE" parentResourceName
-            }
-          }
-        }
-        span(1) {
-          childOf span(0)
-          name controller1Name
-          kind INTERNAL
-          attributes {
-            "$SemanticAttributes.CODE_NAMESPACE" ~/Resource[$]Test*/
-            "$SemanticAttributes.CODE_FUNCTION" "nested"
-          }
-        }
-      }
-    }
-
-    where:
-    resource        | parentResourceName | controller1Name | expectedResponse
-    "/test3/nested" | "/test3/nested"    | "Test3.nested"  | "Test3 nested!"
   }
 
   @Provider

+ 4 - 316
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/testing/src/main/groovy/JaxRsHttpServerTest.groovy

@@ -3,325 +3,13 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import io.opentelemetry.instrumentation.test.AgentTestTrait
-import io.opentelemetry.instrumentation.test.asserts.TraceAssert
-import io.opentelemetry.instrumentation.test.base.HttpServerTest
-import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
-import io.opentelemetry.sdk.trace.data.SpanData
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
-import spock.lang.Unroll
+import java.util.concurrent.TimeUnit
 import test.JaxRsTestResource
 
-import static io.opentelemetry.api.trace.SpanKind.INTERNAL
-import static io.opentelemetry.api.trace.SpanKind.SERVER
-import static io.opentelemetry.api.trace.StatusCode.ERROR
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
-import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP
-import static java.util.concurrent.TimeUnit.SECONDS
-import static org.junit.jupiter.api.Assumptions.assumeTrue
-
-abstract class JaxRsHttpServerTest<S> extends HttpServerTest<S> implements AgentTestTrait {
-
-  def "test super method without @Path"() {
-    given:
-    def response = client.get(address.resolve("test-resource-super").toString()).aggregate().join()
-
-    expect:
-    response.status().code() == SUCCESS.status
-    response.contentUtf8() == SUCCESS.body
-
-    assertTraces(1) {
-      trace(0, 2) {
-        span(0) {
-          hasNoParent()
-          kind SERVER
-          name getContextPath() + "/test-resource-super"
-        }
-        span(1) {
-          name "controller"
-          kind INTERNAL
-          childOf span(0)
-        }
-      }
-    }
-  }
-
-  def "test interface method with @Path"() {
-    assumeTrue(testInterfaceMethodWithPath())
-
-    given:
-    def response = client.get(address.resolve("test-resource-interface/call").toString()).aggregate().join()
-
-    expect:
-    response.status().code() == SUCCESS.status
-    response.contentUtf8() == SUCCESS.body
-
-    assertTraces(1) {
-      trace(0, 2) {
-        span(0) {
-          hasNoParent()
-          kind SERVER
-          name getContextPath() + "/test-resource-interface/call"
-        }
-        span(1) {
-          name "controller"
-          kind INTERNAL
-          childOf span(0)
-        }
-      }
-    }
-  }
-
-  def "test sub resource locator"() {
-    given:
-    def response = client.get(address.resolve("test-sub-resource-locator/call/sub").toString()).aggregate().join()
-
-    expect:
-    response.status().code() == SUCCESS.status
-    response.contentUtf8() == SUCCESS.body
-
-    assertTraces(1) {
-      trace(0, 5) {
-        span(0) {
-          hasNoParent()
-          kind SERVER
-          name getContextPath() + "/test-sub-resource-locator/call/sub"
-        }
-        span(1) {
-          name "JaxRsSubResourceLocatorTestResource.call"
-          kind INTERNAL
-          childOf span(0)
-        }
-        span(2) {
-          name "controller"
-          kind INTERNAL
-          childOf span(1)
-        }
-        span(3) {
-          name "SubResource.call"
-          kind INTERNAL
-          childOf span(0)
-        }
-        span(4) {
-          name "controller"
-          kind INTERNAL
-          childOf span(3)
-        }
-      }
-    }
-  }
-
-  @Unroll
-  def "should handle #desc AsyncResponse"() {
-    given:
-    def url = address.resolve("async?action=${action}").toString()
-
-    when: "async call is started"
-    def futureResponse = client.get(url).aggregate()
-
-    then: "there are no traces yet"
-    assertTraces(0) {
-    }
-
-    when: "barrier is released and resource class sends response"
-    JaxRsTestResource.BARRIER.await(1, SECONDS)
-    def response = futureResponse.join()
-
-    then:
-    response.status().code() == statusCode
-    bodyPredicate(response.contentUtf8())
-
-    def spanCount = 2
-    def hasSendError = asyncCancelHasSendError() && action == "cancel"
-    if (hasSendError) {
-      spanCount++
-    }
-    assertTraces(1) {
-      trace(0, spanCount) {
-        asyncServerSpan(it, 0, url, statusCode)
-        handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage)
-        if (hasSendError) {
-          sendErrorSpan(it, 2, span(1))
-        }
-      }
-    }
-
-    where:
-    desc         | action    | statusCode | bodyPredicate            | isCancelled | isError | errorMessage
-    "successful" | "succeed" | 200        | { it == "success" }      | false       | false   | null
-    "failing"    | "throw"   | 500        | { it == "failure" }      | false       | true    | "failure"
-    "canceled"   | "cancel"  | 503        | { it instanceof String } | true        | false   | null
-  }
-
-  @Unroll
-  def "should handle #desc CompletionStage (JAX-RS 2.1+ only)"() {
-    assumeTrue(shouldTestCompletableStageAsync())
-    given:
-    def url = address.resolve("async-completion-stage?action=${action}").toString()
-
-    when: "async call is started"
-    def futureResponse = client.get(url).aggregate()
-
-    then: "there are no traces yet"
-    assertTraces(0) {
-    }
-
-    when: "barrier is released and resource class sends response"
-    JaxRsTestResource.BARRIER.await(1, SECONDS)
-    def response = futureResponse.join()
-
-    then:
-    response.status().code() == statusCode
-    bodyPredicate(response.contentUtf8())
-
-    assertTraces(1) {
-      trace(0, 2) {
-        asyncServerSpan(it, 0, url, statusCode)
-        handlerSpan(it, 1, span(0), "jaxRs21Async", false, isError, errorMessage)
-      }
-    }
-
-    where:
-    desc         | action    | statusCode | bodyPredicate       | isError | errorMessage
-    "successful" | "succeed" | 200        | { it == "success" } | false   | null
-    "failing"    | "throw"   | 500        | { it == "failure" } | true    | "failure"
-  }
-
-  @Override
-  boolean hasHandlerSpan(ServerEndpoint endpoint) {
-    true
-  }
-
-  @Override
-  boolean testNotFound() {
-    false
-  }
+abstract class JaxRsHttpServerTest<S> extends AbstractJaxRsHttpServerTest<S> {
 
   @Override
-  boolean testPathParam() {
-    true
-  }
-
-  boolean testInterfaceMethodWithPath() {
-    true
-  }
-
-  boolean asyncCancelHasSendError() {
-    false
-  }
-
-  boolean shouldTestCompletableStageAsync() {
-    Boolean.getBoolean("testLatestDeps")
-  }
-
-  @Override
-  void serverSpan(TraceAssert trace,
-                  int index,
-                  String traceID = null,
-                  String parentID = null,
-                  String method = "GET",
-                  Long responseContentLength = null,
-                  ServerEndpoint endpoint = SUCCESS) {
-    serverSpan(trace, index, traceID, parentID, method,
-      endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path,
-      endpoint.resolve(address),
-      endpoint.status,
-      endpoint.query)
-  }
-
-  void asyncServerSpan(TraceAssert trace,
-                       int index,
-                       String url,
-                       int statusCode) {
-    def rawUrl = URI.create(url).toURL()
-    serverSpan(trace, index, null, null, "GET",
-      rawUrl.path,
-      rawUrl.toURI(),
-      statusCode,
-      null)
-  }
-
-  void serverSpan(TraceAssert trace,
-                  int index,
-                  String traceID,
-                  String parentID,
-                  String method,
-                  String path,
-                  URI fullUrl,
-                  int statusCode,
-                  String query) {
-    trace.span(index) {
-      name path
-      kind SERVER
-      if (statusCode >= 500) {
-        status ERROR
-      }
-      if (parentID != null) {
-        traceId traceID
-        parentSpanId parentID
-      } else {
-        hasNoParent()
-      }
-      attributes {
-        "$SemanticAttributes.NET_PEER_IP" { it == null || it == "127.0.0.1" } // Optional
-        "$SemanticAttributes.NET_PEER_PORT" Long
-        "$SemanticAttributes.HTTP_SCHEME" fullUrl.getScheme()
-        "$SemanticAttributes.HTTP_HOST" fullUrl.getHost() + ":" + fullUrl.getPort()
-        "$SemanticAttributes.HTTP_TARGET" fullUrl.getPath() + (fullUrl.getQuery() != null ? "?" + fullUrl.getQuery() : "")
-        "$SemanticAttributes.HTTP_METHOD" method
-        "$SemanticAttributes.HTTP_STATUS_CODE" statusCode
-        "$SemanticAttributes.HTTP_FLAVOR" "1.1"
-        "$SemanticAttributes.HTTP_USER_AGENT" TEST_USER_AGENT
-        "$SemanticAttributes.HTTP_CLIENT_IP" TEST_CLIENT_IP
-        "$SemanticAttributes.NET_TRANSPORT" IP_TCP
-        // Optional
-        "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long }
-        "$SemanticAttributes.HTTP_ROUTE" path
-        if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) {
-          "http.request.header.x_test_request" { it == ["test"] }
-          "http.response.header.x_test_response" { it == ["test"] }
-        }
-      }
-    }
-  }
-
-  @Override
-  void handlerSpan(TraceAssert trace,
-                   int index,
-                   Object parent,
-                   String method = "GET",
-                   ServerEndpoint endpoint = SUCCESS) {
-    handlerSpan(trace, index, parent,
-      endpoint.name().toLowerCase(),
-      false,
-      endpoint == EXCEPTION,
-      EXCEPTION.body)
-  }
-
-  void handlerSpan(TraceAssert trace,
-                   int index,
-                   Object parent,
-                   String methodName,
-                   boolean isCancelled,
-                   boolean isError,
-                   String exceptionMessage = null) {
-    trace.span(index) {
-      name "JaxRsTestResource.${methodName}"
-      kind INTERNAL
-      if (isError) {
-        status ERROR
-        errorEvent(Exception, exceptionMessage)
-      }
-      childOf((SpanData) parent)
-      attributes {
-        "$SemanticAttributes.CODE_NAMESPACE" "test.JaxRsTestResource"
-        "$SemanticAttributes.CODE_FUNCTION" methodName
-        if (isCancelled) {
-          "jaxrs.canceled" true
-        }
-      }
-    }
+  void awaitBarrier(int amount, TimeUnit timeUnit) {
+    JaxRsTestResource.BARRIER.await(amount, timeUnit)
   }
 }

+ 5 - 4
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfRequestContextInstrumentation.java

@@ -15,6 +15,7 @@ import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
 import java.lang.reflect.Method;
 import javax.ws.rs.container.ContainerRequestContext;
 import net.bytebuddy.asm.Advice;
@@ -59,7 +60,7 @@ public class CxfRequestContextInstrumentation implements TypeInstrumentation {
     @Advice.OnMethodEnter(suppress = Throwable.class)
     public static void decorateAbortSpan(
         @Advice.This AbstractRequestContextImpl requestContext,
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope) {
 
@@ -80,9 +81,9 @@ public class CxfRequestContextInstrumentation implements TypeInstrumentation {
       Method method = invocationInfo.getMethodInfo().getMethodToInvoke();
       Class<?> resourceClass = invocationInfo.getRealClass();
 
-      handlerData = new HandlerData(resourceClass, method);
+      handlerData = new Jaxrs2HandlerData(resourceClass, method);
       context =
-          RequestContextHelper.createOrUpdateAbortSpan(
+          Jaxrs2RequestContextHelper.createOrUpdateAbortSpan(
               instrumenter(), (ContainerRequestContext) requestContext, handlerData);
       if (context != null) {
         scope = context.makeCurrent();
@@ -91,7 +92,7 @@ public class CxfRequestContextInstrumentation implements TypeInstrumentation {
 
     @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
     public static void stopSpan(
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope,
         @Advice.Thrown Throwable throwable) {

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfSingletons.java

@@ -6,6 +6,8 @@
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
 
 public final class CxfSingletons {
 

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CxfSpanName.java

@@ -5,7 +5,7 @@
 
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
-import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsPathUtil.normalizePath;
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsPathUtil.normalizePath;
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;

+ 18 - 10
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts

@@ -3,19 +3,19 @@ plugins {
 }
 
 muzzle {
-  // Cant assert fails because muzzle assumes all instrumentations will fail
-  // Instrumentations in jaxrs-2.0-common will pass
   pass {
     group.set("org.glassfish.jersey.core")
     module.set("jersey-server")
     versions.set("[2.0,3.0.0)")
     extraDependency("javax.servlet:javax.servlet-api:3.1.0")
+    assertInverse.set(true)
   }
   pass {
     group.set("org.glassfish.jersey.containers")
     module.set("jersey-container-servlet")
     versions.set("[2.0,3.0.0)")
     extraDependency("javax.servlet:javax.servlet-api:3.1.0")
+    assertInverse.set(true)
   }
 }
 
@@ -30,22 +30,30 @@ dependencies {
   implementation(project(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent"))
 
   testInstrumentation(project(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-annotations:javaagent"))
-
   testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent"))
   testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent"))
 
   testImplementation(project(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:testing"))
-
-  // First version with DropwizardTestSupport:
-  testLibrary("io.dropwizard:dropwizard-testing:0.8.0")
   testImplementation("javax.xml.bind:jaxb-api:2.2.3")
-  testImplementation("com.fasterxml.jackson.module:jackson-module-afterburner")
+  testImplementation("org.eclipse.jetty:jetty-webapp:9.4.6.v20170531")
 
   latestDepTestLibrary("org.glassfish.jersey.core:jersey-server:2.+")
   latestDepTestLibrary("org.glassfish.jersey.containers:jersey-container-servlet:2.+")
-  // this is needed because dropwizard-testing version 0.8.0 (above) pulls it in transitively,
-  // but the latest version of dropwizard-testing does not
-  latestDepTestLibrary("org.eclipse.jetty:jetty-webapp:9.+")
+  latestDepTestLibrary("org.glassfish.jersey.containers:jersey-container-servlet:2.+")
+  latestDepTestLibrary("org.glassfish.jersey.inject:jersey-hk2:2.+")
+}
+
+if (!(findProperty("testLatestDeps") as Boolean)) {
+  // early jersey versions require old guava
+  configurations.testRuntimeClasspath.resolutionStrategy.force("com.google.guava:guava:14.0.1")
+
+  configurations {
+    // early jersey versions bundle asm without shading
+    testImplementation {
+      exclude("org.ow2.asm", "asm")
+      exclude("org.ow2.asm", "asm-commons")
+    }
+  }
 }
 
 tasks {

+ 6 - 4
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseyRequestContextInstrumentation.java

@@ -9,6 +9,7 @@ import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JerseySingle
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
 import java.lang.reflect.Method;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ResourceInfo;
@@ -37,7 +38,7 @@ public class JerseyRequestContextInstrumentation extends AbstractRequestContextI
     @Advice.OnMethodEnter(suppress = Throwable.class)
     public static void decorateAbortSpan(
         @Advice.This ContainerRequestContext requestContext,
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope) {
       UriInfo uriInfo = requestContext.getUriInfo();
@@ -55,9 +56,10 @@ public class JerseyRequestContextInstrumentation extends AbstractRequestContextI
         return;
       }
 
-      handlerData = new HandlerData(resourceClass, method);
+      handlerData = new Jaxrs2HandlerData(resourceClass, method);
       context =
-          RequestContextHelper.createOrUpdateAbortSpan(instrumenter(), requestContext, handlerData);
+          Jaxrs2RequestContextHelper.createOrUpdateAbortSpan(
+              instrumenter(), requestContext, handlerData);
       if (context != null) {
         scope = context.makeCurrent();
       }
@@ -65,7 +67,7 @@ public class JerseyRequestContextInstrumentation extends AbstractRequestContextI
 
     @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
     public static void stopSpan(
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope,
         @Advice.Thrown Throwable throwable) {

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseySingletons.java

@@ -6,6 +6,8 @@
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
 
 public final class JerseySingletons {
 

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseySpanName.java

@@ -5,7 +5,7 @@
 
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
-import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.JaxrsPathUtil.normalizePath;
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsPathUtil.normalizePath;
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;

+ 69 - 19
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyFilterTest.groovy

@@ -3,32 +3,82 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import io.dropwizard.testing.junit.ResourceTestRule
-import org.junit.ClassRule
-import spock.lang.Shared
-
-import javax.ws.rs.client.Entity
-import javax.ws.rs.core.Response
+import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait
+import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse
+import javax.ws.rs.core.Application
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.servlet.ServletContextHandler
+import org.eclipse.jetty.servlet.ServletHolder
+import org.glassfish.jersey.server.ResourceConfig
+import org.glassfish.jersey.servlet.ServletContainer
 
 import static Resource.Test1
 import static Resource.Test2
 import static Resource.Test3
 
-class JerseyFilterTest extends JaxRsFilterTest {
-  @Shared
-  @ClassRule
-  ResourceTestRule resources = ResourceTestRule.builder()
-    .addResource(new Test1())
-    .addResource(new Test2())
-    .addResource(new Test3())
-    .addProvider(simpleRequestFilter)
-    .addProvider(prematchRequestFilter)
-    .build()
+class JerseyFilterTest extends JaxRsFilterTest implements HttpServerTestTrait<Server> {
+
+  def setupSpec() {
+    setupServer()
+  }
+
+  def cleanupSpec() {
+    cleanupServer()
+  }
+
+  @Override
+  Server startServer(int port) {
+    def servlet = new ServletContainer(ResourceConfig.forApplication(new TestApplication()))
+
+    def handler = new ServletContextHandler(ServletContextHandler.SESSIONS)
+    handler.setContextPath("/")
+    handler.addServlet(new ServletHolder(servlet), "/*")
+
+    def server = new Server(port)
+    server.setHandler(handler)
+    server.start()
+
+    return server
+  }
 
   @Override
-  def makeRequest(String url) {
-    Response response = resources.client().target(url).request().post(Entity.text(""))
+  void stopServer(Server httpServer) {
+    httpServer.stop()
+  }
+
+  @Override
+  boolean runsOnServer() {
+    true
+  }
+
+  @Override
+  String defaultServerSpanName() {
+    "/*"
+  }
+
+  @Override
+  def makeRequest(String path) {
+    AggregatedHttpResponse response = client.post(address.resolve(path).toString(), "").aggregate().join()
+
+    return [response.contentUtf8(), response.status().code()]
+  }
+
+  class TestApplication extends Application {
+    @Override
+    Set<Class<?>> getClasses() {
+      def classes = new HashSet()
+      classes.add(Test1)
+      classes.add(Test2)
+      classes.add(Test3)
+      return classes
+    }
 
-    return [response.readEntity(String), response.statusInfo.statusCode]
+    @Override
+    Set<Object> getSingletons() {
+      def singletons = new HashSet()
+      singletons.add(simpleRequestFilter)
+      singletons.add(prematchRequestFilter)
+      return singletons
+    }
   }
 }

+ 14 - 3
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts

@@ -3,9 +3,6 @@ plugins {
 }
 
 muzzle {
-  // Cant assert fails because muzzle assumes all instrumentations will fail
-  // Instrumentations in jaxrs-2.0-common will pass
-
   // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 and then moves it forward again in 4.0.0
   // so the jaxrs-2.0-resteasy-3.0 module applies to [3.0, 3.1) and [3.5, 4.0)
   // and the jaxrs-2.0-resteasy-3.1 module applies to [3.1, 3.5) and [4.0, )
@@ -20,6 +17,20 @@ muzzle {
     module.set("resteasy-jaxrs")
     versions.set("[3.5.0.Final,4)")
   }
+
+  fail {
+    group.set("org.jboss.resteasy")
+    module.set("resteasy-jaxrs")
+    versions.set("(2.1.0.GA,3.0.0.Final)")
+    // missing dependencies
+    skip("2.3.10.Final")
+  }
+
+  fail {
+    group.set("org.jboss.resteasy")
+    module.set("resteasy-core")
+    versions.set("[4.0.0.Final,)")
+  }
 }
 
 dependencies {

+ 6 - 4
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30RequestContextInstrumentation.java

@@ -9,6 +9,7 @@ import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.Resteasy30Si
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
 import java.lang.reflect.Method;
 import javax.ws.rs.container.ContainerRequestContext;
 import net.bytebuddy.asm.Advice;
@@ -38,7 +39,7 @@ public class Resteasy30RequestContextInstrumentation extends AbstractRequestCont
     @Advice.OnMethodEnter(suppress = Throwable.class)
     public static void decorateAbortSpan(
         @Advice.This ContainerRequestContext requestContext,
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope) {
       if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null
@@ -51,9 +52,10 @@ public class Resteasy30RequestContextInstrumentation extends AbstractRequestCont
       Method method = resourceMethodInvoker.getMethod();
       Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
 
-      handlerData = new HandlerData(resourceClass, method);
+      handlerData = new Jaxrs2HandlerData(resourceClass, method);
       context =
-          RequestContextHelper.createOrUpdateAbortSpan(instrumenter(), requestContext, handlerData);
+          Jaxrs2RequestContextHelper.createOrUpdateAbortSpan(
+              instrumenter(), requestContext, handlerData);
       if (context != null) {
         scope = context.makeCurrent();
       }
@@ -61,7 +63,7 @@ public class Resteasy30RequestContextInstrumentation extends AbstractRequestCont
 
     @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
     public static void stopSpan(
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope,
         @Advice.Thrown Throwable throwable) {

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30Singletons.java

@@ -6,6 +6,8 @@
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
 
 public final class Resteasy30Singletons {
 

+ 14 - 3
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts

@@ -3,9 +3,6 @@ plugins {
 }
 
 muzzle {
-  // Cant assert fails because muzzle assumes all instrumentations will fail
-  // Instrumentations in jaxrs-2.0-common will pass
-
   // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 and then moves it forward again in 4.0.0
   // so the jaxrs-2.0-resteasy-3.0 module applies to [3.0, 3.1) and [3.5, 4.0)
   // and the jaxrs-2.0-resteasy-3.1 module applies to [3.1, 3.5) and [4.0, )
@@ -20,6 +17,20 @@ muzzle {
     module.set("resteasy-core")
     versions.set("[4.0.0.Final,6)")
   }
+
+  fail {
+    group.set("org.jboss.resteasy")
+    module.set("resteasy-jaxrs")
+    versions.set("(2.1.0.GA,3.1.0.Final)")
+    // missing dependencies
+    skip("2.3.10.Final")
+  }
+
+  fail {
+    group.set("org.jboss.resteasy")
+    module.set("resteasy-core")
+    versions.set("[6.0.0.Final,)")
+  }
 }
 
 dependencies {

+ 6 - 4
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31RequestContextInstrumentation.java

@@ -9,6 +9,7 @@ import static io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0.Resteasy31Si
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
 import java.lang.reflect.Method;
 import javax.ws.rs.container.ContainerRequestContext;
 import net.bytebuddy.asm.Advice;
@@ -38,7 +39,7 @@ public class Resteasy31RequestContextInstrumentation extends AbstractRequestCont
     @Advice.OnMethodEnter(suppress = Throwable.class)
     public static void decorateAbortSpan(
         @Advice.This ContainerRequestContext requestContext,
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope) {
       if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null
@@ -51,9 +52,10 @@ public class Resteasy31RequestContextInstrumentation extends AbstractRequestCont
       Method method = resourceMethodInvoker.getMethod();
       Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
 
-      handlerData = new HandlerData(resourceClass, method);
+      handlerData = new Jaxrs2HandlerData(resourceClass, method);
       context =
-          RequestContextHelper.createOrUpdateAbortSpan(instrumenter(), requestContext, handlerData);
+          Jaxrs2RequestContextHelper.createOrUpdateAbortSpan(
+              instrumenter(), requestContext, handlerData);
       if (context != null) {
         scope = context.makeCurrent();
       }
@@ -61,7 +63,7 @@ public class Resteasy31RequestContextInstrumentation extends AbstractRequestCont
 
     @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
     public static void stopSpan(
-        @Local("otelHandlerData") HandlerData handlerData,
+        @Local("otelHandlerData") Jaxrs2HandlerData handlerData,
         @Local("otelContext") Context context,
         @Local("otelScope") Scope scope,
         @Advice.Thrown Throwable throwable) {

+ 2 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31Singletons.java

@@ -6,6 +6,8 @@
 package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
 
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
 
 public final class Resteasy31Singletons {
 

+ 1 - 0
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyRootNodeTypeInstrumentation.java

@@ -12,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
 import io.opentelemetry.instrumentation.api.util.VirtualField;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
 import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsPathUtil;
 import net.bytebuddy.asm.Advice;
 import net.bytebuddy.description.type.TypeDescription;
 import net.bytebuddy.implementation.bytecode.assign.Assigner;

+ 22 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/build.gradle.kts

@@ -0,0 +1,22 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+  pass {
+    group.set("jakarta.ws.rs")
+    module.set("jakarta.ws.rs-api")
+    versions.set("[3.0.0,)")
+    assertInverse.set(true)
+  }
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+
+  implementation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:javaagent"))
+
+  compileOnly("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+
+  testImplementation("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+}

+ 60 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ContainerRequestFilterInstrumentation.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * This adds the filter class name to the request properties. The class name is used by <code>
+ * DefaultRequestContextInstrumentation</code>
+ */
+public class ContainerRequestFilterInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<ClassLoader> classLoaderOptimization() {
+    return hasClassesNamed("jakarta.ws.rs.container.ContainerRequestFilter");
+  }
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return implementsInterface(named("jakarta.ws.rs.container.ContainerRequestFilter"));
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        isMethod()
+            .and(named("filter"))
+            .and(takesArguments(1))
+            .and(takesArgument(0, named("jakarta.ws.rs.container.ContainerRequestContext"))),
+        ContainerRequestFilterInstrumentation.class.getName() + "$RequestFilterAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class RequestFilterAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void setFilterClass(
+        @Advice.This ContainerRequestFilter filter,
+        @Advice.Argument(0) ContainerRequestContext context) {
+      context.setProperty(JaxrsConstants.ABORT_FILTER_CLASS, filter.getClass());
+    }
+  }
+}

+ 95 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/DefaultRequestContextInstrumentation.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0.JaxrsAnnotationsSingletons.instrumenter;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsServerSpanNaming;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.Advice.Local;
+
+/**
+ * Default context instrumentation.
+ *
+ * <p>JAX-RS does not define a way to get the matched resource method from the <code>
+ * ContainerRequestContext</code>
+ *
+ * <p>This default instrumentation uses the class name of the filter to create the span. More
+ * specific instrumentations may override this value.
+ */
+public class DefaultRequestContextInstrumentation extends AbstractRequestContextInstrumentation {
+  @Override
+  protected String abortAdviceName() {
+    return getClass().getName() + "$ContainerRequestContextAdvice";
+  }
+
+  @SuppressWarnings("unused")
+  public static class ContainerRequestContextAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void createGenericSpan(
+        @Advice.This ContainerRequestContext requestContext,
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope) {
+      if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null) {
+        return;
+      }
+
+      Class<?> filterClass =
+          (Class<?>) requestContext.getProperty(JaxrsConstants.ABORT_FILTER_CLASS);
+      Method method = null;
+      try {
+        method = filterClass.getMethod("filter", ContainerRequestContext.class);
+      } catch (NoSuchMethodException e) {
+        // Unable to find the filter method.  This should not be reachable because the context
+        // can only be aborted inside the filter method
+      }
+
+      if (filterClass == null || method == null) {
+        return;
+      }
+
+      Context parentContext = Java8BytecodeBridge.currentContext();
+      handlerData = new Jaxrs3HandlerData(filterClass, method);
+
+      HttpRouteHolder.updateHttpRoute(
+          parentContext,
+          HttpRouteSource.CONTROLLER,
+          JaxrsServerSpanNaming.SERVER_SPAN_NAME,
+          handlerData);
+
+      if (!instrumenter().shouldStart(parentContext, handlerData)) {
+        return;
+      }
+
+      context = instrumenter().start(parentContext, handlerData);
+      scope = context.makeCurrent();
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope,
+        @Advice.Thrown Throwable throwable) {
+      if (scope == null) {
+        return;
+      }
+
+      scope.close();
+      instrumenter().end(context, handlerData, null, throwable);
+    }
+  }
+}

+ 171 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsInstrumentation.java

@@ -0,0 +1,171 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperMethod;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0.JaxrsAnnotationsSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.bootstrap.CallDepth;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.AsyncResponseData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.CompletionStageFinishCallback;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsServerSpanNaming;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.container.AsyncResponse;
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletionStage;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JaxrsAnnotationsInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<ClassLoader> classLoaderOptimization() {
+    return hasClassesNamed("jakarta.ws.rs.Path");
+  }
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return hasSuperType(
+        isAnnotatedWith(named("jakarta.ws.rs.Path"))
+            .or(declaresMethod(isAnnotatedWith(named("jakarta.ws.rs.Path")))));
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        isMethod()
+            .and(
+                hasSuperMethod(
+                    isAnnotatedWith(
+                        namedOneOf(
+                            "jakarta.ws.rs.Path",
+                            "jakarta.ws.rs.DELETE",
+                            "jakarta.ws.rs.GET",
+                            "jakarta.ws.rs.HEAD",
+                            "jakarta.ws.rs.OPTIONS",
+                            "jakarta.ws.rs.PATCH",
+                            "jakarta.ws.rs.POST",
+                            "jakarta.ws.rs.PUT")))),
+        JaxrsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class JaxRsAnnotationsAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void nameSpan(
+        @Advice.This Object target,
+        @Advice.Origin Method method,
+        @Advice.AllArguments Object[] args,
+        @Advice.Local("otelCallDepth") CallDepth callDepth,
+        @Advice.Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Advice.Local("otelContext") Context context,
+        @Advice.Local("otelScope") Scope scope,
+        @Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {
+      callDepth = CallDepth.forClass(Path.class);
+      if (callDepth.getAndIncrement() > 0) {
+        return;
+      }
+
+      VirtualField<AsyncResponse, AsyncResponseData> virtualField = null;
+      for (Object arg : args) {
+        if (arg instanceof AsyncResponse) {
+          asyncResponse = (AsyncResponse) arg;
+          virtualField = VirtualField.find(AsyncResponse.class, AsyncResponseData.class);
+          if (virtualField.get(asyncResponse) != null) {
+            /*
+             * We are probably in a recursive call and don't want to start a new span because it
+             * would replace the existing span in the asyncResponse and cause it to never finish. We
+             * could work around this by using a list instead, but we likely don't want the extra
+             * span anyway.
+             */
+            return;
+          }
+          break;
+        }
+      }
+
+      Context parentContext = Java8BytecodeBridge.currentContext();
+      handlerData = new Jaxrs3HandlerData(target.getClass(), method);
+
+      HttpRouteHolder.updateHttpRoute(
+          parentContext,
+          HttpRouteSource.CONTROLLER,
+          JaxrsServerSpanNaming.SERVER_SPAN_NAME,
+          handlerData);
+
+      if (!instrumenter().shouldStart(parentContext, handlerData)) {
+        return;
+      }
+
+      context = instrumenter().start(parentContext, handlerData);
+      scope = context.makeCurrent();
+
+      if (virtualField != null && asyncResponse != null) {
+        virtualField.set(asyncResponse, AsyncResponseData.create(context, handlerData));
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(
+        @Advice.Return(readOnly = false, typing = Typing.DYNAMIC) Object returnValue,
+        @Advice.Thrown Throwable throwable,
+        @Advice.Local("otelCallDepth") CallDepth callDepth,
+        @Advice.Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Advice.Local("otelContext") Context context,
+        @Advice.Local("otelScope") Scope scope,
+        @Advice.Local("otelAsyncResponse") AsyncResponse asyncResponse) {
+      if (callDepth.decrementAndGet() > 0) {
+        return;
+      }
+
+      if (scope == null) {
+        return;
+      }
+
+      scope.close();
+
+      if (throwable != null) {
+        instrumenter().end(context, handlerData, null, throwable);
+        return;
+      }
+
+      CompletionStage<?> asyncReturnValue =
+          returnValue instanceof CompletionStage ? (CompletionStage<?>) returnValue : null;
+
+      if (asyncResponse != null && !asyncResponse.isSuspended()) {
+        // Clear span from the asyncResponse. Logically this should never happen. Added to be safe.
+        VirtualField.find(AsyncResponse.class, AsyncResponseData.class).set(asyncResponse, null);
+      }
+      if (asyncReturnValue != null) {
+        // span finished by CompletionStageFinishCallback
+        asyncReturnValue =
+            asyncReturnValue.handle(
+                new CompletionStageFinishCallback<>(instrumenter(), context, handlerData));
+      }
+      if ((asyncResponse == null || !asyncResponse.isSuspended()) && asyncReturnValue == null) {
+        instrumenter().end(context, handlerData, null, null);
+      }
+      // else span finished by AsyncResponse*Advice
+    }
+  }
+}

+ 37 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsInstrumentationModule.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static java.util.Arrays.asList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+import net.bytebuddy.matcher.ElementMatcher;
+
+@AutoService(InstrumentationModule.class)
+public class JaxrsAnnotationsInstrumentationModule extends InstrumentationModule {
+  public JaxrsAnnotationsInstrumentationModule() {
+    super("jaxrs-annotations", "jaxrs-3.0-annotations");
+  }
+
+  // require jax-rs 3
+  @Override
+  public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
+    return hasClassesNamed("jakarta.ws.rs.container.AsyncResponse");
+  }
+
+  @Override
+  public List<TypeInstrumentation> typeInstrumentations() {
+    return asList(
+        new ContainerRequestFilterInstrumentation(),
+        new DefaultRequestContextInstrumentation(),
+        new JaxrsAnnotationsInstrumentation(),
+        new JaxrsAsyncResponseInstrumentation());
+  }
+}

+ 22 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAnnotationsSingletons.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
+
+public final class JaxrsAnnotationsSingletons {
+
+  private static final Instrumenter<HandlerData, Void> INSTANCE =
+      JaxrsInstrumenterFactory.createInstrumenter("io.opentelemetry.jaxrs-annotations-3.0");
+
+  public static Instrumenter<HandlerData, Void> instrumenter() {
+    return INSTANCE;
+  }
+
+  private JaxrsAnnotationsSingletons() {}
+}

+ 107 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JaxrsAsyncResponseInstrumentation.java

@@ -0,0 +1,107 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0.JaxrsAnnotationsSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.AsyncResponseData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConfig;
+import jakarta.ws.rs.container.AsyncResponse;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JaxrsAsyncResponseInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<ClassLoader> classLoaderOptimization() {
+    return hasClassesNamed("jakarta.ws.rs.container.AsyncResponse");
+  }
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return implementsInterface(named("jakarta.ws.rs.container.AsyncResponse"));
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("resume").and(takesArgument(0, Object.class)).and(isPublic()),
+        JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseAdvice");
+    transformer.applyAdviceToMethod(
+        named("resume").and(takesArgument(0, Throwable.class)).and(isPublic()),
+        JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseThrowableAdvice");
+    transformer.applyAdviceToMethod(
+        named("cancel"),
+        JaxrsAsyncResponseInstrumentation.class.getName() + "$AsyncResponseCancelAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class AsyncResponseAdvice {
+
+    @Advice.OnMethodExit(suppress = Throwable.class)
+    public static void stopSpan(@Advice.This AsyncResponse asyncResponse) {
+
+      VirtualField<AsyncResponse, AsyncResponseData> virtualField =
+          VirtualField.find(AsyncResponse.class, AsyncResponseData.class);
+
+      AsyncResponseData data = virtualField.get(asyncResponse);
+      if (data != null) {
+        virtualField.set(asyncResponse, null);
+        instrumenter().end(data.getContext(), data.getHandlerData(), null, null);
+      }
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public static class AsyncResponseThrowableAdvice {
+
+    @Advice.OnMethodExit(suppress = Throwable.class)
+    public static void stopSpan(
+        @Advice.This AsyncResponse asyncResponse, @Advice.Argument(0) Throwable throwable) {
+
+      VirtualField<AsyncResponse, AsyncResponseData> virtualField =
+          VirtualField.find(AsyncResponse.class, AsyncResponseData.class);
+
+      AsyncResponseData data = virtualField.get(asyncResponse);
+      if (data != null) {
+        virtualField.set(asyncResponse, null);
+        instrumenter().end(data.getContext(), data.getHandlerData(), null, throwable);
+      }
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public static class AsyncResponseCancelAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void stopSpan(@Advice.This AsyncResponse asyncResponse) {
+
+      VirtualField<AsyncResponse, AsyncResponseData> virtualField =
+          VirtualField.find(AsyncResponse.class, AsyncResponseData.class);
+
+      AsyncResponseData data = virtualField.get(asyncResponse);
+      if (data != null) {
+        virtualField.set(asyncResponse, null);
+        Context context = data.getContext();
+        if (JaxrsConfig.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
+          Java8BytecodeBridge.spanFromContext(context).setAttribute("jaxrs.canceled", true);
+        }
+        instrumenter().end(context, data.getHandlerData(), null, null);
+      }
+    }
+  }
+}

+ 162 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/test/groovy/JaxrsAnnotationsInstrumentationTest.groovy

@@ -0,0 +1,162 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
+import spock.lang.Unroll
+
+import jakarta.ws.rs.DELETE
+import jakarta.ws.rs.GET
+import jakarta.ws.rs.HEAD
+import jakarta.ws.rs.OPTIONS
+import jakarta.ws.rs.POST
+import jakarta.ws.rs.PUT
+import jakarta.ws.rs.Path
+
+import static io.opentelemetry.api.trace.SpanKind.SERVER
+import static io.opentelemetry.instrumentation.test.utils.ClassUtils.getClassName
+
+class JaxrsAnnotationsInstrumentationTest extends AgentInstrumentationSpecification {
+
+  @Unroll
+  def "span named '#paramName' from annotations on class '#className' when is not root span"() {
+    setup:
+    runWithHttpServerSpan("test") {
+      obj.call()
+    }
+
+    expect:
+    assertTraces(1) {
+      trace(0, 2) {
+        span(0) {
+          name paramName
+          kind SERVER
+          hasNoParent()
+          attributes {
+            "$SemanticAttributes.HTTP_ROUTE" paramName
+          }
+        }
+        span(1) {
+          name "${className}.call"
+          childOf span(0)
+          attributes {
+            "$SemanticAttributes.CODE_NAMESPACE" obj.getClass().getName()
+            "$SemanticAttributes.CODE_FUNCTION" "call"
+          }
+        }
+      }
+    }
+
+    when: "multiple calls to the same method"
+    runWithHttpServerSpan("test") {
+      (1..10).each {
+        obj.call()
+      }
+    }
+    then: "doesn't increase the cache size"
+
+    where:
+    paramName      | obj
+    "/a"           | new Jax() {
+      @Path("/a")
+      void call() {
+      }
+    }
+    "/b"           | new Jax() {
+      @GET
+      @Path("/b")
+      void call() {
+      }
+    }
+    "/interface/c" | new InterfaceWithPath() {
+      @POST
+      @Path("/c")
+      void call() {
+      }
+    }
+    "/interface"   | new InterfaceWithPath() {
+      @HEAD
+      void call() {
+      }
+    }
+    "/abstract/d"  | new AbstractClassWithPath() {
+      @POST
+      @Path("/d")
+      void call() {
+      }
+    }
+    "/abstract"    | new AbstractClassWithPath() {
+      @PUT
+      void call() {
+      }
+    }
+    "/child/e"     | new ChildClassWithPath() {
+      @OPTIONS
+      @Path("/e")
+      void call() {
+      }
+    }
+    "/child/call"  | new ChildClassWithPath() {
+      @DELETE
+      void call() {
+      }
+    }
+    "/child/call"  | new ChildClassWithPath()
+    "/child/call"  | new JavaInterfaces.ChildClassOnInterface()
+    "/child/call"  | new JavaInterfaces.DefaultChildClassOnInterface()
+
+    className = getClassName(obj.class)
+  }
+
+  def "no annotations has no effect"() {
+    setup:
+    runWithHttpServerSpan("test") {
+      obj.call()
+    }
+
+    expect:
+    assertTraces(1) {
+      trace(0, 1) {
+        span(0) {
+          name "test"
+          kind SERVER
+          attributes {
+          }
+        }
+      }
+    }
+
+    where:
+    obj | _
+    new Jax() {
+      void call() {
+      }
+    }   | _
+  }
+
+  interface Jax {
+    void call()
+  }
+
+  @Path("/interface")
+  interface InterfaceWithPath extends Jax {
+    @GET
+    void call()
+  }
+
+  @Path("/abstract")
+  static abstract class AbstractClassWithPath implements Jax {
+    @PUT
+    abstract void call()
+  }
+
+  @Path("child")
+  static class ChildClassWithPath extends AbstractClassWithPath {
+    @Path("call")
+    @POST
+    void call() {
+    }
+  }
+}

+ 69 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations/javaagent/src/test/java/JavaInterfaces.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+public class JavaInterfaces {
+
+  interface Jax {
+
+    void call();
+  }
+
+  @Path("interface")
+  interface InterfaceWithClassMethodPath extends Jax {
+
+    @Override
+    @GET
+    @Path("invoke")
+    void call();
+  }
+
+  @Path("abstract")
+  abstract static class AbstractClassOnInterfaceWithClassPath
+      implements InterfaceWithClassMethodPath {
+
+    @GET
+    @Path("call")
+    @Override
+    public void call() {
+      // do nothing
+    }
+
+    abstract void actual();
+  }
+
+  @Path("child")
+  static class ChildClassOnInterface extends AbstractClassOnInterfaceWithClassPath {
+
+    @Override
+    void actual() {
+      // do nothing
+    }
+  }
+
+  @Path("interface")
+  interface DefaultInterfaceWithClassMethodPath extends Jax {
+
+    @Override
+    @GET
+    @Path("call")
+    default void call() {
+      actual();
+    }
+
+    void actual();
+  }
+
+  @Path("child")
+  static class DefaultChildClassOnInterface implements DefaultInterfaceWithClassMethodPath {
+
+    @Override
+    public void actual() {
+      // do nothing
+    }
+  }
+}

+ 10 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/build.gradle.kts

@@ -0,0 +1,10 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+  api(project(":instrumentation:jaxrs:jaxrs-common:javaagent"))
+
+  compileOnly("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+}

+ 42 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/AbstractRequestContextInstrumentation.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public abstract class AbstractRequestContextInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<ClassLoader> classLoaderOptimization() {
+    return hasClassesNamed("jakarta.ws.rs.container.ContainerRequestContext");
+  }
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return implementsInterface(named("jakarta.ws.rs.container.ContainerRequestContext"));
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        isMethod()
+            .and(named("abortWith"))
+            .and(takesArguments(1))
+            .and(takesArgument(0, named("jakarta.ws.rs.core.Response"))),
+        abortAdviceName());
+  }
+
+  protected abstract String abortAdviceName();
+}

+ 60 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/Jaxrs3HandlerData.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.Path;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+public class Jaxrs3HandlerData extends HandlerData {
+
+  private static final ClassValue<Map<Method, String>> serverSpanNames =
+      new ClassValue<Map<Method, String>>() {
+        @Override
+        protected Map<Method, String> computeValue(Class<?> type) {
+          return new ConcurrentHashMap<>();
+        }
+      };
+
+  public Jaxrs3HandlerData(Class<?> target, Method method) {
+    super(target, method);
+  }
+
+  /**
+   * Returns the span name given a JaxRS annotated method. Results are cached so this method can be
+   * called multiple times without significantly impacting performance.
+   *
+   * @return The result can be an empty string but will never be {@code null}.
+   */
+  @Override
+  public String getServerSpanName() {
+    Map<Method, String> classMap = serverSpanNames.get(target);
+    String spanName = classMap.get(method);
+    if (spanName == null) {
+      spanName = super.getServerSpanName();
+      classMap.put(method, spanName);
+    }
+
+    return spanName;
+  }
+
+  @Override
+  protected Class<? extends Annotation> getHttpMethodAnnotation() {
+    return HttpMethod.class;
+  }
+
+  @Override
+  protected Supplier<String> getPathAnnotation(AnnotatedElement annotatedElement) {
+    Path path = annotatedElement.getAnnotation(Path.class);
+    return path != null ? path::value : null;
+  }
+}

+ 28 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/Jaxrs3RequestContextHelper.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.RequestContextHelper;
+import jakarta.ws.rs.container.ContainerRequestContext;
+
+public final class Jaxrs3RequestContextHelper {
+  public static <T extends HandlerData> Context createOrUpdateAbortSpan(
+      Instrumenter<T, Void> instrumenter, ContainerRequestContext requestContext, T handlerData) {
+
+    if (handlerData == null) {
+      return null;
+    }
+
+    requestContext.setProperty(JaxrsConstants.ABORT_HANDLED, true);
+    return RequestContextHelper.createOrUpdateAbortSpan(instrumenter, handlerData);
+  }
+
+  private Jaxrs3RequestContextHelper() {}
+}

+ 10 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/build.gradle.kts

@@ -0,0 +1,10 @@
+plugins {
+  id("otel.java-conventions")
+}
+
+dependencies {
+  api(project(":instrumentation:jaxrs:jaxrs-common:testing"))
+  api("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+
+  compileOnly("org.eclipse.jetty:jetty-webapp:11.0.0")
+}

+ 62 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsFilterTest.groovy

@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.ws.rs.container.ContainerRequestContext
+import jakarta.ws.rs.container.ContainerRequestFilter
+import jakarta.ws.rs.container.PreMatching
+import jakarta.ws.rs.core.MediaType
+import jakarta.ws.rs.core.Response
+import jakarta.ws.rs.ext.Provider
+import spock.lang.Shared
+import spock.lang.Unroll
+
+@Unroll
+abstract class JaxRsFilterTest extends AbstractJaxRsFilterTest {
+
+  @Shared
+  SimpleRequestFilter simpleRequestFilter = new SimpleRequestFilter()
+
+  @Shared
+  PrematchRequestFilter prematchRequestFilter = new PrematchRequestFilter()
+
+  @Override
+  void setAbortStatus(boolean abortNormal, boolean abortPrematch) {
+    simpleRequestFilter.abort = abortNormal
+    prematchRequestFilter.abort = abortPrematch
+  }
+
+  @Provider
+  static class SimpleRequestFilter implements ContainerRequestFilter {
+    boolean abort = false
+
+    @Override
+    void filter(ContainerRequestContext requestContext) throws IOException {
+      if (abort) {
+        requestContext.abortWith(
+          Response.status(Response.Status.UNAUTHORIZED)
+            .entity("Aborted")
+            .type(MediaType.TEXT_PLAIN_TYPE)
+            .build())
+      }
+    }
+  }
+
+  @Provider
+  @PreMatching
+  static class PrematchRequestFilter implements ContainerRequestFilter {
+    boolean abort = false
+
+    @Override
+    void filter(ContainerRequestContext requestContext) throws IOException {
+      if (abort) {
+        requestContext.abortWith(
+          Response.status(Response.Status.UNAUTHORIZED)
+            .entity("Aborted Prematch")
+            .type(MediaType.TEXT_PLAIN_TYPE)
+            .build())
+      }
+    }
+  }
+}

+ 15 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsHttpServerTest.groovy

@@ -0,0 +1,15 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import java.util.concurrent.TimeUnit
+import test.JaxRsTestResource
+
+abstract class JaxRsHttpServerTest<S> extends AbstractJaxRsHttpServerTest<S> {
+
+  @Override
+  void awaitBarrier(int amount, TimeUnit timeUnit) {
+    JaxRsTestResource.BARRIER.await(amount, timeUnit)
+  }
+}

+ 41 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/JaxRsJettyHttpServerTest.groovy

@@ -0,0 +1,41 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.webapp.WebAppContext
+
+import static org.eclipse.jetty.util.resource.Resource.newResource
+
+abstract class JaxRsJettyHttpServerTest extends JaxRsHttpServerTest<Server> {
+
+  @Override
+  Server startServer(int port) {
+    WebAppContext webAppContext = new WebAppContext()
+    webAppContext.setContextPath("/")
+    // set up test application
+    webAppContext.setBaseResource(newResource("src/test/webapp"))
+
+    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() {
+    "/rest-app"
+  }
+}

+ 239 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/groovy/test/JaxRsTestResource.groovy

@@ -0,0 +1,239 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package test
+
+import io.opentelemetry.instrumentation.test.base.HttpServerTest
+import jakarta.ws.rs.ApplicationPath
+import jakarta.ws.rs.GET
+import jakarta.ws.rs.HeaderParam
+import jakarta.ws.rs.Path
+import jakarta.ws.rs.PathParam
+import jakarta.ws.rs.QueryParam
+import jakarta.ws.rs.container.AsyncResponse
+import jakarta.ws.rs.container.Suspended
+import jakarta.ws.rs.core.Application
+import jakarta.ws.rs.core.Context
+import jakarta.ws.rs.core.Response
+import jakarta.ws.rs.core.UriInfo
+import jakarta.ws.rs.ext.ExceptionMapper
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionStage
+import java.util.concurrent.CyclicBarrier
+
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
+import static java.util.concurrent.TimeUnit.SECONDS
+
+@Path("")
+class JaxRsTestResource {
+  @Path("/success")
+  @GET
+  String success() {
+    HttpServerTest.controller(SUCCESS) {
+      SUCCESS.body
+    }
+  }
+
+  @Path("query")
+  @GET
+  String query_param(@QueryParam("some") String param) {
+    HttpServerTest.controller(QUERY_PARAM) {
+      "some=$param"
+    }
+  }
+
+  @Path("redirect")
+  @GET
+  Response redirect(@Context UriInfo uriInfo) {
+    HttpServerTest.controller(REDIRECT) {
+      Response.status(Response.Status.FOUND)
+        .location(uriInfo.relativize(new URI(REDIRECT.body)))
+        .build()
+    }
+  }
+
+  @Path("error-status")
+  @GET
+  Response error() {
+    HttpServerTest.controller(ERROR) {
+      Response.status(ERROR.status)
+        .entity(ERROR.body)
+        .build()
+    }
+  }
+
+  @Path("exception")
+  @GET
+  Object exception() {
+    HttpServerTest.controller(EXCEPTION) {
+      throw new Exception(EXCEPTION.body)
+    }
+  }
+
+  @Path("path/{id}/param")
+  @GET
+  String path_param(@PathParam("id") int id) {
+    HttpServerTest.controller(PATH_PARAM) {
+      id
+    }
+  }
+
+  @GET
+  @Path("captureHeaders")
+  Response capture_headers(@HeaderParam("X-Test-Request") String header) {
+    HttpServerTest.controller(CAPTURE_HEADERS) {
+      Response.status(CAPTURE_HEADERS.status)
+        .header("X-Test-Response", header)
+        .entity(CAPTURE_HEADERS.body)
+        .build()
+    }
+  }
+
+  @Path("/child")
+  @GET
+  void indexed_child(@Context UriInfo uriInfo, @Suspended AsyncResponse response) {
+    def parameters = uriInfo.queryParameters
+
+    CompletableFuture.runAsync({
+      HttpServerTest.controller(INDEXED_CHILD) {
+        INDEXED_CHILD.collectSpanAttributes { parameters.getFirst(it) }
+        response.resume("")
+      }
+    })
+  }
+
+  static final BARRIER = new CyclicBarrier(2)
+
+  @Path("async")
+  @GET
+  void asyncOp(@Suspended AsyncResponse response, @QueryParam("action") String action) {
+    CompletableFuture.runAsync({
+      // await for the test method to verify that there are no spans yet
+      BARRIER.await(1, SECONDS)
+
+      switch (action) {
+        case "succeed":
+          response.resume("success")
+          break
+        case "throw":
+          response.resume(new Exception("failure"))
+          break
+        case "cancel":
+          response.cancel()
+          break
+        default:
+          response.resume(new AssertionError((Object) ("invalid action value: " + action)))
+          break
+      }
+    })
+  }
+
+  @Path("async-completion-stage")
+  @GET
+  CompletionStage<String> jaxRs21Async(@QueryParam("action") String action) {
+    def result = new CompletableFuture<String>()
+    CompletableFuture.runAsync({
+      // await for the test method to verify that there are no spans yet
+      BARRIER.await(1, SECONDS)
+
+      switch (action) {
+        case "succeed":
+          result.complete("success")
+          break
+        case "throw":
+          result.completeExceptionally(new Exception("failure"))
+          break
+        default:
+          result.completeExceptionally(new AssertionError((Object) ("invalid action value: " + action)))
+          break
+      }
+    })
+    result
+  }
+}
+
+@Path("test-resource-super")
+class JaxRsSuperClassTestResource extends JaxRsSuperClassTestResourceSuper {
+}
+
+class JaxRsSuperClassTestResourceSuper {
+  @GET
+  Object call() {
+    HttpServerTest.controller(SUCCESS) {
+      SUCCESS.body
+    }
+  }
+}
+
+class JaxRsInterfaceClassTestResource extends JaxRsInterfaceClassTestResourceSuper implements JaxRsInterface {
+}
+
+@Path("test-resource-interface")
+interface JaxRsInterface {
+  @Path("call")
+  @GET
+  Object call()
+}
+
+class JaxRsInterfaceClassTestResourceSuper {
+  Object call() {
+    HttpServerTest.controller(SUCCESS) {
+      SUCCESS.body
+    }
+  }
+}
+
+@Path("test-sub-resource-locator")
+class JaxRsSubResourceLocatorTestResource {
+  @Path("call")
+  Object call() {
+    HttpServerTest.controller(SUCCESS) {
+      return new SubResource()
+    }
+  }
+}
+
+class SubResource {
+  @Path("sub")
+  @GET
+  String call() {
+    HttpServerTest.controller(SUCCESS) {
+      SUCCESS.body
+    }
+  }
+}
+
+class JaxRsTestExceptionMapper implements ExceptionMapper<Exception> {
+  @Override
+  Response toResponse(Exception exception) {
+    return Response.status(500)
+      .entity(exception.message)
+      .build()
+  }
+}
+
+class JaxRsTestApplication extends Application {
+  @Override
+  Set<Class<?>> getClasses() {
+    def classes = new HashSet()
+    classes.add(JaxRsTestResource)
+    classes.add(JaxRsSuperClassTestResource)
+    classes.add(JaxRsInterfaceClassTestResource)
+    classes.add(JaxRsSubResourceLocatorTestResource)
+    classes.add(JaxRsTestExceptionMapper)
+    return classes
+  }
+}
+
+@ApplicationPath("/rest-app")
+class JaxRsApplicationPathTestApplication extends JaxRsTestApplication {
+}

+ 53 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-common/testing/src/main/java/Resource.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+
+@Path("/ignored")
+public interface Resource {
+  @Path("ignored")
+  String hello(String name);
+
+  @Path("/test")
+  interface SubResource extends Cloneable, Resource {
+    @Override
+    @POST
+    @Path("/hello/{name}")
+    String hello(@PathParam("name") String name);
+  }
+
+  class Test1 implements SubResource {
+    @Override
+    public String hello(String name) {
+      return "Test1 " + name + "!";
+    }
+  }
+
+  @Path("/test2")
+  class Test2 implements SubResource {
+    @Override
+    public String hello(String name) {
+      return "Test2 " + name + "!";
+    }
+  }
+
+  @Path("/test3")
+  class Test3 implements SubResource {
+    @Override
+    @POST
+    @Path("/hi/{name}")
+    public String hello(@PathParam("name") String name) {
+      return "Test3 " + name + "!";
+    }
+
+    @POST
+    @Path("/nested")
+    public String nested() {
+      return hello("nested");
+    }
+  }
+}

+ 41 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/build.gradle.kts

@@ -0,0 +1,41 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+  pass {
+    group.set("org.glassfish.jersey.core")
+    module.set("jersey-server")
+    versions.set("[3.0.0,)")
+    assertInverse.set(true)
+    extraDependency("jakarta.servlet:jakarta.servlet-api:5.0.0")
+  }
+}
+
+otelJava {
+  minJavaVersionSupported.set(JavaVersion.VERSION_11)
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+
+  compileOnly("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+  compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0")
+  library("org.glassfish.jersey.core:jersey-server:3.0.0")
+  library("org.glassfish.jersey.containers:jersey-container-servlet:3.0.0")
+  library("org.glassfish.jersey.inject:jersey-hk2:3.0.0")
+  implementation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:javaagent"))
+
+  testInstrumentation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-annotations:javaagent"))
+  testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent"))
+
+  testImplementation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:testing"))
+  testImplementation("org.eclipse.jetty:jetty-webapp:11.0.0")
+}
+
+tasks {
+  withType<Test>().configureEach {
+    // TODO run tests both with and without experimental span attributes
+    jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true")
+  }
+}

+ 28 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyInstrumentationModule.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static java.util.Arrays.asList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class JerseyInstrumentationModule extends InstrumentationModule {
+  public JerseyInstrumentationModule() {
+    super("jaxrs", "jaxrs-2.0", "jersey", "jersey-2.0");
+  }
+
+  @Override
+  public List<TypeInstrumentation> typeInstrumentations() {
+    return asList(
+        new JerseyRequestContextInstrumentation(),
+        new JerseyServletContainerInstrumentation(),
+        new JerseyResourceMethodDispatcherInstrumentation());
+  }
+}

+ 81 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyRequestContextInstrumentation.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0.JerseySingletons.instrumenter;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.UriInfo;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.Advice.Local;
+
+/**
+ * Jersey specific context instrumentation.
+ *
+ * <p>JAX-RS does not define a way to get the matched resource method from the <code>
+ * ContainerRequestContext</code>
+ *
+ * <p>In the Jersey implementation, <code>UriInfo</code> implements <code>ResourceInfo</code>. The
+ * matched resource method can be retrieved from that object
+ */
+public class JerseyRequestContextInstrumentation extends AbstractRequestContextInstrumentation {
+  @Override
+  protected String abortAdviceName() {
+    return getClass().getName() + "$ContainerRequestContextAdvice";
+  }
+
+  @SuppressWarnings("unused")
+  public static class ContainerRequestContextAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void decorateAbortSpan(
+        @Advice.This ContainerRequestContext requestContext,
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope) {
+      UriInfo uriInfo = requestContext.getUriInfo();
+
+      if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null
+          || !(uriInfo instanceof ResourceInfo)) {
+        return;
+      }
+
+      ResourceInfo resourceInfo = (ResourceInfo) uriInfo;
+      Method method = resourceInfo.getResourceMethod();
+      Class<?> resourceClass = resourceInfo.getResourceClass();
+
+      if (resourceClass == null || method == null) {
+        return;
+      }
+
+      handlerData = new Jaxrs3HandlerData(resourceClass, method);
+      context =
+          Jaxrs3RequestContextHelper.createOrUpdateAbortSpan(
+              instrumenter(), requestContext, handlerData);
+      if (context != null) {
+        scope = context.makeCurrent();
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope,
+        @Advice.Thrown Throwable throwable) {
+      if (scope == null) {
+        return;
+      }
+      scope.close();
+      instrumenter().end(context, handlerData, null, throwable);
+    }
+  }
+}

+ 47 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyResourceMethodDispatcherInstrumentation.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import jakarta.ws.rs.core.Request;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JerseyResourceMethodDispatcherInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("dispatch")
+            .and(
+                takesArgument(
+                    1,
+                    namedOneOf(
+                        "javax.ws.rs.core.Request",
+                        "org.glassfish.jersey.server.ContainerRequest"))),
+        JerseyResourceMethodDispatcherInstrumentation.class.getName() + "$DispatchAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class DispatchAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void onEnter(@Advice.Argument(1) Request request) {
+      JerseySpanName.INSTANCE.updateServerSpanName(request);
+    }
+  }
+}

+ 62 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseyServletContainerInstrumentation.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import jakarta.servlet.http.HttpServletRequest;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JerseyServletContainerInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.glassfish.jersey.servlet.ServletContainer");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        isMethod()
+            .and(named("service"))
+            .and(takesArgument(0, named("jakarta.servlet.http.HttpServletRequest")))
+            .and(takesArgument(1, named("jakarta.servlet.http.HttpServletResponse"))),
+        JerseyServletContainerInstrumentation.class.getName() + "$ServiceAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class ServiceAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void onEnter(
+        @Advice.Argument(0) HttpServletRequest httpServletRequest,
+        @Advice.Local("otelScope") Scope scope) {
+      Context context =
+          JaxrsContextPath.init(
+              Java8BytecodeBridge.currentContext(), httpServletRequest.getServletPath());
+      if (context != null) {
+        scope = context.makeCurrent();
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
+      if (scope != null) {
+        scope.close();
+      }
+    }
+  }
+}

+ 22 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseySingletons.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
+
+public final class JerseySingletons {
+
+  private static final Instrumenter<HandlerData, Void> INSTANCE =
+      JaxrsInstrumenterFactory.createInstrumenter("io.opentelemetry.jersey-2.0");
+
+  public static Instrumenter<HandlerData, Void> instrumenter() {
+    return INSTANCE;
+  }
+
+  private JerseySingletons() {}
+}

+ 43 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/JerseySpanName.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsPathUtil.normalizePath;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
+import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
+import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
+import jakarta.ws.rs.core.Request;
+import jakarta.ws.rs.core.UriInfo;
+import javax.annotation.Nullable;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+
+public class JerseySpanName implements HttpRouteGetter<Request> {
+
+  public static final JerseySpanName INSTANCE = new JerseySpanName();
+
+  public void updateServerSpanName(Request request) {
+    Context context = Context.current();
+    HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.NESTED_CONTROLLER, this, request);
+  }
+
+  @Override
+  @Nullable
+  public String get(Context context, Request request) {
+    ContainerRequest containerRequest = (ContainerRequest) request;
+    UriInfo uriInfo = containerRequest.getUriInfo();
+    ExtendedUriInfo extendedUriInfo = (ExtendedUriInfo) uriInfo;
+    return extendedUriInfo.getMatchedTemplates().stream()
+        .map((uriTemplate) -> normalizePath(uriTemplate.getTemplate()))
+        .reduce((a, b) -> b + a)
+        .map(s -> ServletContextPath.prepend(context, JaxrsContextPath.prepend(context, s)))
+        .orElse(null);
+  }
+}

+ 84 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyFilterTest.groovy

@@ -0,0 +1,84 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait
+import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse
+import jakarta.ws.rs.core.Application
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.servlet.ServletContextHandler
+import org.eclipse.jetty.servlet.ServletHolder
+import org.glassfish.jersey.server.ResourceConfig
+import org.glassfish.jersey.servlet.ServletContainer
+
+import static Resource.Test1
+import static Resource.Test2
+import static Resource.Test3
+
+class JerseyFilterTest extends JaxRsFilterTest implements HttpServerTestTrait<Server> {
+
+  def setupSpec() {
+    setupServer()
+  }
+
+  def cleanupSpec() {
+    cleanupServer()
+  }
+
+  @Override
+  Server startServer(int port) {
+    def servlet = new ServletContainer(ResourceConfig.forApplication(new TestApplication()))
+
+    def handler = new ServletContextHandler(ServletContextHandler.SESSIONS)
+    handler.setContextPath("/")
+    handler.addServlet(new ServletHolder(servlet), "/*")
+
+    def server = new Server(port)
+    server.setHandler(handler)
+    server.start()
+
+    return server
+  }
+
+  @Override
+  void stopServer(Server httpServer) {
+    httpServer.stop()
+  }
+
+  @Override
+  boolean runsOnServer() {
+    true
+  }
+
+  @Override
+  String defaultServerSpanName() {
+    "/*"
+  }
+
+  @Override
+  def makeRequest(String path) {
+    AggregatedHttpResponse response = client.post(address.resolve(path).toString(), "").aggregate().join()
+
+    return [response.contentUtf8(), response.status().code()]
+  }
+
+  class TestApplication extends Application {
+    @Override
+    Set<Class<?>> getClasses() {
+      def classes = new HashSet()
+      classes.add(Test1)
+      classes.add(Test2)
+      classes.add(Test3)
+      return classes
+    }
+
+    @Override
+    Set<Object> getSingletons() {
+      def singletons = new HashSet()
+      singletons.add(simpleRequestFilter)
+      singletons.add(prematchRequestFilter)
+      return singletons
+    }
+  }
+}

+ 45 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy

@@ -0,0 +1,45 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.servlet.ServletContextHandler
+import org.eclipse.jetty.servlet.ServletHolder
+import org.glassfish.jersey.server.ResourceConfig
+import org.glassfish.jersey.servlet.ServletContainer
+import test.JaxRsTestApplication
+
+class JerseyHttpServerTest extends JaxRsHttpServerTest<Server> {
+
+  @Override
+  Server startServer(int port) {
+    def servlet = new ServletContainer(ResourceConfig.forApplicationClass(JaxRsTestApplication))
+
+    def handler = new ServletContextHandler(ServletContextHandler.SESSIONS)
+    handler.setContextPath("/")
+    handler.addServlet(new ServletHolder(servlet), "/*")
+
+    def server = new Server(port)
+    server.setHandler(handler)
+    server.start()
+
+    return server
+  }
+
+  @Override
+  void stopServer(Server httpServer) {
+    httpServer.stop()
+  }
+
+  @Override
+  boolean asyncCancelHasSendError() {
+    true
+  }
+
+  @Override
+  boolean testInterfaceMethodWithPath() {
+    // disables a test that jersey deems invalid
+    false
+  }
+}

+ 18 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy

@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+class JerseyJettyHttpServerTest extends JaxRsJettyHttpServerTest {
+
+  @Override
+  boolean asyncCancelHasSendError() {
+    true
+  }
+
+  @Override
+  boolean testInterfaceMethodWithPath() {
+    // disables a test that jersey deems invalid
+    false
+  }
+}

+ 23 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/groovy/JerseyStartupListener.groovy

@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.servlet.ServletContextEvent
+import jakarta.servlet.ServletContextListener
+import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer
+import test.JaxRsApplicationPathTestApplication
+
+// ServletContainerInitializer isn't automatically called due to the way this test is set up
+// so we call it ourself
+class JerseyStartupListener implements ServletContextListener {
+  @Override
+  void contextInitialized(ServletContextEvent servletContextEvent) {
+    new JerseyServletContainerInitializer().onStartup(Collections.singleton(JaxRsApplicationPathTestApplication),
+      servletContextEvent.getServletContext())
+  }
+
+  @Override
+  void contextDestroyed(ServletContextEvent servletContextEvent) {
+  }
+}

+ 10 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0/javaagent/src/test/webapp/WEB-INF/web.xml

@@ -0,0 +1,10 @@
+<?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>JerseyStartupListener</listener-class>
+  </listener>
+</web-app>

+ 43 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts

@@ -0,0 +1,43 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+  pass {
+    group.set("org.jboss.resteasy")
+    module.set("resteasy-core")
+    versions.set("[6.0.0.Final,)")
+    assertInverse.set(true)
+  }
+}
+
+otelJava {
+  minJavaVersionSupported.set(JavaVersion.VERSION_11)
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+
+  compileOnly("jakarta.ws.rs:jakarta.ws.rs-api:3.0.0")
+  library("org.jboss.resteasy:resteasy-core:6.0.0.Final")
+
+  implementation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:javaagent"))
+
+  testInstrumentation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-annotations:javaagent"))
+  testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent"))
+
+  testImplementation(project(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:testing"))
+  testImplementation("org.eclipse.jetty:jetty-webapp:11.0.0")
+  testLibrary("org.jboss.resteasy:resteasy-undertow:6.0.0.Final") {
+    exclude("org.jboss.resteasy", "resteasy-client")
+  }
+  testLibrary("io.undertow:undertow-servlet-jakarta:2.2.17.Final")
+  testLibrary("org.jboss.resteasy:resteasy-servlet-initializer:6.0.0.Final")
+}
+
+tasks {
+  withType<Test>().configureEach {
+    // TODO run tests both with and without experimental span attributes
+    jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true")
+  }
+}

+ 30 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyInstrumentationModule.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static java.util.Arrays.asList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class ResteasyInstrumentationModule extends InstrumentationModule {
+  public ResteasyInstrumentationModule() {
+    super("jaxrs", "jaxrs-3.0", "resteasy", "resteasy-6.0");
+  }
+
+  @Override
+  public List<TypeInstrumentation> typeInstrumentations() {
+    return asList(
+        new ResteasyRequestContextInstrumentation(),
+        new ResteasyServletContainerDispatcherInstrumentation(),
+        new ResteasyRootNodeTypeInstrumentation(),
+        new ResteasyResourceMethodInvokerInstrumentation(),
+        new ResteasyResourceLocatorInvokerInstrumentation());
+  }
+}

+ 75 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyRequestContextInstrumentation.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsConstants;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.Advice.Local;
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+import org.jboss.resteasy.core.interception.jaxrs.PostMatchContainerRequestContext;
+
+/**
+ * RESTEasy specific context instrumentation.
+ *
+ * <p>JAX-RS does not define a way to get the matched resource method from the <code>
+ * ContainerRequestContext</code>
+ *
+ * <p>In the RESTEasy implementation, <code>ContainerRequestContext</code> is implemented by <code>
+ * PostMatchContainerRequestContext</code>. This class provides a way to get the matched resource
+ * method through <code>getResourceMethod()</code>.
+ */
+public class ResteasyRequestContextInstrumentation extends AbstractRequestContextInstrumentation {
+  @Override
+  protected String abortAdviceName() {
+    return getClass().getName() + "$ContainerRequestContextAdvice";
+  }
+
+  @SuppressWarnings("unused")
+  public static class ContainerRequestContextAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void decorateAbortSpan(
+        @Advice.This ContainerRequestContext requestContext,
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope) {
+      if (requestContext.getProperty(JaxrsConstants.ABORT_HANDLED) != null
+          || !(requestContext instanceof PostMatchContainerRequestContext)) {
+        return;
+      }
+
+      ResourceMethodInvoker resourceMethodInvoker =
+          ((PostMatchContainerRequestContext) requestContext).getResourceMethod();
+      Method method = resourceMethodInvoker.getMethod();
+      Class<?> resourceClass = resourceMethodInvoker.getResourceClass();
+
+      handlerData = new Jaxrs3HandlerData(resourceClass, method);
+      context =
+          Jaxrs3RequestContextHelper.createOrUpdateAbortSpan(
+              ResteasySingletons.instrumenter(), requestContext, handlerData);
+      if (context != null) {
+        scope = context.makeCurrent();
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(
+        @Local("otelHandlerData") Jaxrs3HandlerData handlerData,
+        @Local("otelContext") Context context,
+        @Local("otelScope") Scope scope,
+        @Advice.Thrown Throwable throwable) {
+      if (scope == null) {
+        return;
+      }
+      scope.close();
+      ResteasySingletons.instrumenter().end(context, handlerData, null, throwable);
+    }
+  }
+}

+ 70 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyResourceLocatorInvokerInstrumentation.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.jboss.resteasy.core.ResourceLocatorInvoker;
+
+public class ResteasyResourceLocatorInvokerInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.jboss.resteasy.core.ResourceLocatorInvoker");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("invokeOnTargetObject")
+            .and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest")))
+            .and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse")))
+            .and(takesArgument(2, Object.class)),
+        ResteasyResourceLocatorInvokerInstrumentation.class.getName()
+            + "$InvokeOnTargetObjectAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class InvokeOnTargetObjectAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void onEnter(
+        @Advice.This ResourceLocatorInvoker resourceInvoker,
+        @Advice.Local("otelScope") Scope scope) {
+
+      Context currentContext = Java8BytecodeBridge.currentContext();
+
+      String name =
+          VirtualField.find(ResourceLocatorInvoker.class, String.class).get(resourceInvoker);
+      ResteasySpanName.INSTANCE.updateServerSpanName(currentContext, name);
+
+      // subresource locator returns a resources class that may have @Path annotations
+      // append current path to jax-rs context path so that it would be present in the final path
+      Context context =
+          JaxrsContextPath.init(currentContext, JaxrsContextPath.prepend(currentContext, name));
+      if (context != null) {
+        scope = context.makeCurrent();
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
+      if (scope != null) {
+        scope.close();
+      }
+    }
+  }
+}

+ 47 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyResourceMethodInvokerInstrumentation.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+
+public class ResteasyResourceMethodInvokerInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.jboss.resteasy.core.ResourceMethodInvoker");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("invokeOnTarget")
+            .and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest")))
+            .and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse")))
+            .and(takesArgument(2, Object.class)),
+        ResteasyResourceMethodInvokerInstrumentation.class.getName() + "$InvokeOnTargetAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class InvokeOnTargetAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void onEnter(@Advice.This ResourceMethodInvoker resourceInvoker) {
+
+      String name =
+          VirtualField.find(ResourceMethodInvoker.class, String.class).get(resourceInvoker);
+      ResteasySpanName.INSTANCE.updateServerSpanName(Java8BytecodeBridge.currentContext(), name);
+    }
+  }
+}

+ 63 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyRootNodeTypeInstrumentation.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsPathUtil;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.jboss.resteasy.core.ResourceLocatorInvoker;
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+
+public class ResteasyRootNodeTypeInstrumentation implements TypeInstrumentation {
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.jboss.resteasy.core.registry.RootNode");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("addInvoker")
+            .and(takesArgument(0, String.class))
+            // package of ResourceInvoker was changed in reasteasy 4
+            .and(
+                takesArgument(
+                    1,
+                    namedOneOf(
+                        "org.jboss.resteasy.core.ResourceInvoker",
+                        "org.jboss.resteasy.spi.ResourceInvoker"))),
+        ResteasyRootNodeTypeInstrumentation.class.getName() + "$AddInvokerAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class AddInvokerAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void addInvoker(
+        @Advice.Argument(0) String path,
+        @Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) Object invoker) {
+      String normalizedPath = JaxrsPathUtil.normalizePath(path);
+      if (invoker instanceof ResourceLocatorInvoker) {
+        ResourceLocatorInvoker resourceLocatorInvoker = (ResourceLocatorInvoker) invoker;
+        VirtualField.find(ResourceLocatorInvoker.class, String.class)
+            .set(resourceLocatorInvoker, normalizedPath);
+      } else if (invoker instanceof ResourceMethodInvoker) {
+        ResourceMethodInvoker resourceMethodInvoker = (ResourceMethodInvoker) invoker;
+        VirtualField.find(ResourceMethodInvoker.class, String.class)
+            .set(resourceMethodInvoker, normalizedPath);
+      }
+    }
+  }
+}

+ 56 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasyServletContainerDispatcherInstrumentation.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class ResteasyServletContainerDispatcherInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        isMethod().and(named("service")),
+        ResteasyServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice");
+  }
+
+  @SuppressWarnings("unused")
+  public static class ServiceAdvice {
+
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static void onEnter(
+        @Advice.FieldValue("servletMappingPrefix") String servletMappingPrefix,
+        @Advice.Local("otelScope") Scope scope) {
+      Context context =
+          JaxrsContextPath.init(Java8BytecodeBridge.currentContext(), servletMappingPrefix);
+      if (context != null) {
+        scope = context.makeCurrent();
+      }
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void stopSpan(@Advice.Local("otelScope") Scope scope) {
+      if (scope != null) {
+        scope.close();
+      }
+    }
+  }
+}

+ 22 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasySingletons.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.HandlerData;
+import io.opentelemetry.javaagent.instrumentation.jaxrs.JaxrsInstrumenterFactory;
+
+public final class ResteasySingletons {
+
+  private static final Instrumenter<HandlerData, Void> INSTANCE =
+      JaxrsInstrumenterFactory.createInstrumenter("io.opentelemetry.resteasy-6.0");
+
+  public static Instrumenter<HandlerData, Void> instrumenter() {
+    return INSTANCE;
+  }
+
+  private ResteasySingletons() {}
+}

+ 37 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v3_0/ResteasySpanName.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs.v3_0;
+
+import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource.NESTED_CONTROLLER;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;
+import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
+import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;
+import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath;
+import javax.annotation.Nullable;
+
+public final class ResteasySpanName implements HttpRouteGetter<String> {
+
+  public static final ResteasySpanName INSTANCE = new ResteasySpanName();
+
+  public void updateServerSpanName(Context context, String name) {
+    if (name != null) {
+      HttpRouteHolder.updateHttpRoute(context, NESTED_CONTROLLER, this, name);
+    }
+  }
+
+  @Override
+  @Nullable
+  public String get(Context context, String name) {
+    if (name.isEmpty()) {
+      return null;
+    }
+    return ServletContextPath.prepend(context, JaxrsContextPath.prepend(context, name));
+  }
+
+  private ResteasySpanName() {}
+}

+ 43 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyFilterTest.groovy

@@ -0,0 +1,43 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.ws.rs.core.MediaType
+import org.jboss.resteasy.mock.MockDispatcherFactory
+import org.jboss.resteasy.mock.MockHttpRequest
+import org.jboss.resteasy.mock.MockHttpResponse
+import spock.lang.Shared
+
+import static Resource.Test1
+import static Resource.Test2
+import static Resource.Test3
+
+class ResteasyFilterTest extends JaxRsFilterTest {
+  @Shared
+  def dispatcher
+
+  def setupSpec() {
+    dispatcher = MockDispatcherFactory.createDispatcher()
+    def registry = dispatcher.getRegistry()
+    registry.addSingletonResource(new Test1())
+    registry.addSingletonResource(new Test2())
+    registry.addSingletonResource(new Test3())
+
+    dispatcher.getProviderFactory().register(simpleRequestFilter)
+    dispatcher.getProviderFactory().register(prematchRequestFilter)
+  }
+
+  @Override
+  def makeRequest(String url) {
+    MockHttpRequest request = MockHttpRequest.post(url)
+    request.contentType(MediaType.TEXT_PLAIN_TYPE)
+    request.content(new byte[0])
+
+    MockHttpResponse response = new MockHttpResponse()
+    dispatcher.invoke(request, response)
+
+    return [response.contentAsString, response.status]
+  }
+
+}

+ 35 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyHttpServerTest.groovy

@@ -0,0 +1,35 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.undertow.Undertow
+import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer
+import test.JaxRsTestApplication
+
+class ResteasyHttpServerTest extends JaxRsHttpServerTest<UndertowJaxrsServer> {
+
+  @Override
+  String getContextPath() {
+    "/resteasy-context"
+  }
+
+  @Override
+  UndertowJaxrsServer startServer(int port) {
+    def server = new UndertowJaxrsServer()
+    server.deploy(JaxRsTestApplication, getContextPath())
+    server.start(Undertow.builder()
+      .addHttpListener(port, "localhost"))
+    return server
+  }
+
+  @Override
+  void stopServer(UndertowJaxrsServer server) {
+    server.stop()
+  }
+
+  // resteasy 3.0.x does not support JAX-RS 2.1
+  boolean shouldTestCompletableStageAsync() {
+    false
+  }
+}

+ 7 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyJettyHttpServerTest.groovy

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

+ 24 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/groovy/ResteasyStartupListener.groovy

@@ -0,0 +1,24 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer
+import test.JaxRsApplicationPathTestApplication
+
+import jakarta.servlet.ServletContextEvent
+import jakarta.servlet.ServletContextListener
+
+// ServletContainerInitializer isn't automatically called due to the way this test is set up
+// so we call it ourself
+class ResteasyStartupListener implements ServletContextListener {
+  @Override
+  void contextInitialized(ServletContextEvent servletContextEvent) {
+    new ResteasyServletInitializer().onStartup(Collections.singleton(JaxRsApplicationPathTestApplication),
+      servletContextEvent.getServletContext())
+  }
+
+  @Override
+  void contextDestroyed(ServletContextEvent servletContextEvent) {
+  }
+}

+ 10 - 0
instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/src/test/webapp/WEB-INF/web.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="5.0" metadata-complete="false"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns="https://jakarta.ee/xml/ns/jakartaee"
+  xsi:schemaLocation= "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd">
+
+  <listener>
+    <listener-class>ResteasyStartupListener</listener-class>
+  </listener>
+</web-app>

+ 10 - 0
instrumentation/jaxrs/jaxrs-common/javaagent/build.gradle.kts

@@ -0,0 +1,10 @@
+plugins {
+  id("otel.javaagent-instrumentation")
+}
+
+dependencies {
+  bootstrap(project(":instrumentation:jaxrs:jaxrs-common:bootstrap"))
+
+  compileOnly("com.google.auto.value:auto-value-annotations")
+  annotationProcessor("com.google.auto.value:auto-value")
+}

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/AsyncResponseData.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/AsyncResponseData.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import com.google.auto.value.AutoValue;
 import io.opentelemetry.context.Context;

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/CompletionStageFinishCallback.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/CompletionStageFinishCallback.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;

+ 141 - 0
instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/HandlerData.java

@@ -0,0 +1,141 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
+
+import io.opentelemetry.javaagent.bootstrap.jaxrs.ClassHierarchyIterable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+public abstract class HandlerData {
+
+  protected final Class<?> target;
+  protected final Method method;
+
+  protected HandlerData(Class<?> target, Method method) {
+    this.target = target;
+    this.method = method;
+  }
+
+  public Class<?> codeClass() {
+    return target;
+  }
+
+  public String methodName() {
+    return method.getName();
+  }
+
+  public String getServerSpanName() {
+    String httpMethod = null;
+    String methodPath = null;
+    String classPath = findClassPath(target);
+    for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
+      Method currentMethod;
+      if (currentClass.equals(target)) {
+        currentMethod = method;
+      } else {
+        currentMethod = findMatchingMethod(method, currentClass.getDeclaredMethods());
+      }
+
+      if (currentMethod != null) {
+        if (httpMethod == null) {
+          httpMethod = locateHttpMethod(currentMethod);
+        }
+        if (methodPath == null) {
+          methodPath = findMethodPath(currentMethod);
+        }
+
+        if (httpMethod != null && methodPath != null) {
+          break;
+        }
+      }
+    }
+    return buildSpanName(classPath, methodPath);
+  }
+
+  protected abstract Class<? extends Annotation> getHttpMethodAnnotation();
+
+  private String locateHttpMethod(Method method) {
+    String httpMethod = null;
+    for (Annotation ann : method.getDeclaredAnnotations()) {
+      if (ann.annotationType().getAnnotation(getHttpMethodAnnotation()) != null) {
+        httpMethod = ann.annotationType().getSimpleName();
+      }
+    }
+    return httpMethod;
+  }
+
+  private String findMethodPath(Method method) {
+    Supplier<String> pathSupplier = getPathAnnotation(method);
+    return pathSupplier != null ? pathSupplier.get() : null;
+  }
+
+  protected abstract Supplier<String> getPathAnnotation(AnnotatedElement annotated);
+
+  private String findClassPath(Class<?> target) {
+    for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
+      Supplier<String> pathSupplier = getPathAnnotation(currentClass);
+      if (pathSupplier != null) {
+        // Annotation overridden, no need to continue.
+        return pathSupplier.get();
+      }
+    }
+
+    return null;
+  }
+
+  private static Method findMatchingMethod(Method baseMethod, Method[] methods) {
+    nextMethod:
+    for (Method method : methods) {
+      if (!baseMethod.getReturnType().equals(method.getReturnType())) {
+        continue;
+      }
+
+      if (!baseMethod.getName().equals(method.getName())) {
+        continue;
+      }
+
+      Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
+      Class<?>[] parameterTypes = method.getParameterTypes();
+      if (baseParameterTypes.length != parameterTypes.length) {
+        continue;
+      }
+      for (int i = 0; i < baseParameterTypes.length; i++) {
+        if (!baseParameterTypes[i].equals(parameterTypes[i])) {
+          continue nextMethod;
+        }
+      }
+      return method;
+    }
+    return null;
+  }
+
+  private static String buildSpanName(String classPath, String methodPath) {
+    StringBuilder spanNameBuilder = new StringBuilder();
+    boolean skipSlash = false;
+    if (classPath != null) {
+      if (!classPath.startsWith("/")) {
+        spanNameBuilder.append("/");
+      }
+      spanNameBuilder.append(classPath);
+      skipSlash = classPath.endsWith("/") || classPath.isEmpty();
+    }
+
+    if (methodPath != null) {
+      if (skipSlash) {
+        if (methodPath.startsWith("/")) {
+          methodPath = methodPath.length() == 1 ? "" : methodPath.substring(1);
+        }
+      } else if (!methodPath.startsWith("/")) {
+        spanNameBuilder.append("/");
+      }
+      spanNameBuilder.append(methodPath);
+    }
+
+    return spanNameBuilder.toString().trim();
+  }
+}

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsCodeAttributesGetter.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsCodeAttributesGetter.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesGetter;
 

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsConfig.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsConfig.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.instrumentation.api.config.Config;
 

+ 15 - 0
instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsConstants.java

@@ -0,0 +1,15 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
+
+public final class JaxrsConstants {
+  public static final String ABORT_FILTER_CLASS =
+      "io.opentelemetry.javaagent.instrumentation.jaxrs.filter.abort.class";
+  public static final String ABORT_HANDLED =
+      "io.opentelemetry.javaagent.instrumentation.jaxrs.filter.abort.handled";
+
+  private JaxrsConstants() {}
+}

+ 2 - 2
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsInstrumenterFactory.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsInstrumenterFactory.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.instrumentation.api.config.ExperimentalConfig;
@@ -11,7 +11,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
 import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
 import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor;
 
-final class JaxrsInstrumenterFactory {
+public final class JaxrsInstrumenterFactory {
 
   public static Instrumenter<HandlerData, Void> createInstrumenter(String instrumentationName) {
     JaxrsCodeAttributesGetter codeAttributesGetter = new JaxrsCodeAttributesGetter();

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsPathUtil.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsPathUtil.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 public final class JaxrsPathUtil {
   private JaxrsPathUtil() {}

+ 1 - 1
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsServerSpanNaming.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/JaxrsServerSpanNaming.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteGetter;
 import io.opentelemetry.javaagent.bootstrap.jaxrs.JaxrsContextPath;

+ 3 - 11
instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/RequestContextHelper.java → instrumentation/jaxrs/jaxrs-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/RequestContextHelper.java

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0;
+package io.opentelemetry.javaagent.instrumentation.jaxrs;
 
 import io.opentelemetry.api.trace.Span;
 import io.opentelemetry.context.Context;
@@ -12,19 +12,11 @@ import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
 import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
 import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
-import javax.ws.rs.container.ContainerRequestContext;
 
 public final class RequestContextHelper {
-  public static Context createOrUpdateAbortSpan(
-      Instrumenter<HandlerData, Void> instrumenter,
-      ContainerRequestContext requestContext,
-      HandlerData handlerData) {
+  public static <T extends HandlerData> Context createOrUpdateAbortSpan(
+      Instrumenter<T, Void> instrumenter, T handlerData) {
 
-    if (handlerData == null) {
-      return null;
-    }
-
-    requestContext.setProperty(JaxrsConstants.ABORT_HANDLED, true);
     Context parentContext = Java8BytecodeBridge.currentContext();
     Span serverSpan = LocalRootSpan.fromContextOrNull(parentContext);
     Span currentSpan = Java8BytecodeBridge.spanFromContext(parentContext);

+ 16 - 0
instrumentation/jaxrs/jaxrs-common/testing/build.gradle.kts

@@ -0,0 +1,16 @@
+plugins {
+  id("otel.java-conventions")
+}
+
+dependencies {
+  api(project(":testing-common"))
+
+  implementation("org.apache.groovy:groovy")
+  implementation("io.opentelemetry:opentelemetry-api")
+  implementation("org.spockframework:spock-core")
+  implementation("org.slf4j:slf4j-api")
+  implementation("ch.qos.logback:logback-classic")
+  implementation("org.slf4j:log4j-over-slf4j")
+  implementation("org.slf4j:jcl-over-slf4j")
+  implementation("org.slf4j:jul-to-slf4j")
+}

+ 147 - 0
instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsFilterTest.groovy

@@ -0,0 +1,147 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
+import org.junit.jupiter.api.Assumptions
+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.UNSET
+
+@Unroll
+abstract class AbstractJaxRsFilterTest extends AgentInstrumentationSpecification {
+
+  abstract makeRequest(String url)
+
+  Tuple2<String, String> runRequest(String resource) {
+    if (runsOnServer()) {
+      return makeRequest(resource)
+    }
+    // start a trace because the test doesn't go through any servlet or other instrumentation.
+    return runWithHttpServerSpan("test.span") {
+      makeRequest(resource)
+    }
+  }
+
+  boolean testAbortPrematch() {
+    true
+  }
+
+  boolean runsOnServer() {
+    false
+  }
+
+  String defaultServerSpanName() {
+    "test.span"
+  }
+
+  abstract void setAbortStatus(boolean abortNormal, boolean abortPrematch)
+
+  def "test #resource, #abortNormal, #abortPrematch"() {
+    Assumptions.assumeTrue(!abortPrematch || testAbortPrematch())
+
+    given:
+    setAbortStatus(abortNormal, abortPrematch)
+    def abort = abortNormal || abortPrematch
+
+    when:
+
+    def (responseText, responseStatus) = runRequest(resource)
+
+    then:
+    responseText == expectedResponse
+
+    if (abort) {
+      responseStatus == 401 // Response.Status.UNAUTHORIZED.statusCode
+    } else {
+      responseStatus == 200 // Response.Status.OK.statusCode
+    }
+
+    assertTraces(1) {
+      trace(0, 2) {
+        span(0) {
+          name parentSpanName != null ? parentSpanName : defaultServerSpanName()
+          kind SERVER
+          if (runsOnServer() && abortNormal) {
+            status UNSET
+          }
+        }
+        span(1) {
+          childOf span(0)
+          name controllerName
+          if (abortPrematch) {
+            attributes {
+              "$SemanticAttributes.CODE_NAMESPACE" "JaxRsFilterTest\$PrematchRequestFilter"
+              "$SemanticAttributes.CODE_FUNCTION" "filter"
+            }
+          } else {
+            attributes {
+              "$SemanticAttributes.CODE_NAMESPACE" ~/Resource[$]Test*/
+              "$SemanticAttributes.CODE_FUNCTION" "hello"
+            }
+          }
+        }
+      }
+    }
+
+    where:
+    resource           | abortNormal | abortPrematch | parentSpanName        | controllerName                 | expectedResponse
+    "/test/hello/bob"  | false       | false         | "/test/hello/{name}"  | "Test1.hello"                  | "Test1 bob!"
+    "/test2/hello/bob" | false       | false         | "/test2/hello/{name}" | "Test2.hello"                  | "Test2 bob!"
+    "/test3/hi/bob"    | false       | false         | "/test3/hi/{name}"    | "Test3.hello"                  | "Test3 bob!"
+
+    // Resteasy and Jersey give different resource class names for just the below case
+    // Resteasy returns "SubResource.class"
+    // Jersey returns "Test1.class
+    // "/test/hello/bob"  | true        | false         | "/test/hello/{name}"  | "Test1.hello"                  | "Aborted"
+
+    "/test2/hello/bob" | true        | false         | "/test2/hello/{name}" | "Test2.hello"                  | "Aborted"
+    "/test3/hi/bob"    | true        | false         | "/test3/hi/{name}"    | "Test3.hello"                  | "Aborted"
+    "/test/hello/bob"  | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
+    "/test2/hello/bob" | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
+    "/test3/hi/bob"    | false       | true          | null                  | "PrematchRequestFilter.filter" | "Aborted Prematch"
+  }
+
+  def "test nested call"() {
+    given:
+    setAbortStatus(false, false)
+
+    when:
+    def (responseText, responseStatus) = runRequest(resource)
+
+    then:
+    responseStatus == 200 // Response.Status.OK.statusCode
+    responseText == expectedResponse
+
+    assertTraces(1) {
+      trace(0, 2) {
+        span(0) {
+          name parentResourceName
+          kind SERVER
+          if (!runsOnServer()) {
+            attributes {
+              "$SemanticAttributes.HTTP_ROUTE" parentResourceName
+            }
+          }
+        }
+        span(1) {
+          childOf span(0)
+          name controller1Name
+          kind INTERNAL
+          attributes {
+            "$SemanticAttributes.CODE_NAMESPACE" ~/Resource[$]Test*/
+            "$SemanticAttributes.CODE_FUNCTION" "nested"
+          }
+        }
+      }
+    }
+
+    where:
+    resource        | parentResourceName | controller1Name | expectedResponse
+    "/test3/nested" | "/test3/nested"    | "Test3.nested"  | "Test3 nested!"
+  }
+}

+ 329 - 0
instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy

@@ -0,0 +1,329 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.AgentTestTrait
+import io.opentelemetry.instrumentation.test.asserts.TraceAssert
+import io.opentelemetry.instrumentation.test.base.HttpServerTest
+import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
+import io.opentelemetry.sdk.trace.data.SpanData
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
+import java.util.concurrent.TimeUnit
+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
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
+import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP
+import static java.util.concurrent.TimeUnit.SECONDS
+import static org.junit.jupiter.api.Assumptions.assumeTrue
+
+abstract class AbstractJaxRsHttpServerTest<S> extends HttpServerTest<S> implements AgentTestTrait {
+
+  abstract void awaitBarrier(int amount, TimeUnit timeUnit)
+
+  def "test super method without @Path"() {
+    given:
+    def response = client.get(address.resolve("test-resource-super").toString()).aggregate().join()
+
+    expect:
+    response.status().code() == SUCCESS.status
+    response.contentUtf8() == SUCCESS.body
+
+    assertTraces(1) {
+      trace(0, 2) {
+        span(0) {
+          hasNoParent()
+          kind SERVER
+          name getContextPath() + "/test-resource-super"
+        }
+        span(1) {
+          name "controller"
+          kind INTERNAL
+          childOf span(0)
+        }
+      }
+    }
+  }
+
+  def "test interface method with @Path"() {
+    assumeTrue(testInterfaceMethodWithPath())
+
+    given:
+    def response = client.get(address.resolve("test-resource-interface/call").toString()).aggregate().join()
+
+    expect:
+    response.status().code() == SUCCESS.status
+    response.contentUtf8() == SUCCESS.body
+
+    assertTraces(1) {
+      trace(0, 2) {
+        span(0) {
+          hasNoParent()
+          kind SERVER
+          name getContextPath() + "/test-resource-interface/call"
+        }
+        span(1) {
+          name "controller"
+          kind INTERNAL
+          childOf span(0)
+        }
+      }
+    }
+  }
+
+  def "test sub resource locator"() {
+    given:
+    def response = client.get(address.resolve("test-sub-resource-locator/call/sub").toString()).aggregate().join()
+
+    expect:
+    response.status().code() == SUCCESS.status
+    response.contentUtf8() == SUCCESS.body
+
+    assertTraces(1) {
+      trace(0, 5) {
+        span(0) {
+          hasNoParent()
+          kind SERVER
+          name getContextPath() + "/test-sub-resource-locator/call/sub"
+        }
+        span(1) {
+          name "JaxRsSubResourceLocatorTestResource.call"
+          kind INTERNAL
+          childOf span(0)
+        }
+        span(2) {
+          name "controller"
+          kind INTERNAL
+          childOf span(1)
+        }
+        span(3) {
+          name "SubResource.call"
+          kind INTERNAL
+          childOf span(0)
+        }
+        span(4) {
+          name "controller"
+          kind INTERNAL
+          childOf span(3)
+        }
+      }
+    }
+  }
+
+  @Unroll
+  def "should handle #desc AsyncResponse"() {
+    given:
+    def url = address.resolve("async?action=${action}").toString()
+
+    when: "async call is started"
+    def futureResponse = client.get(url).aggregate()
+
+    then: "there are no traces yet"
+    assertTraces(0) {
+    }
+
+    when: "barrier is released and resource class sends response"
+    awaitBarrier(1, SECONDS)
+    def response = futureResponse.join()
+
+    then:
+    response.status().code() == statusCode
+    bodyPredicate(response.contentUtf8())
+
+    def spanCount = 2
+    def hasSendError = asyncCancelHasSendError() && action == "cancel"
+    if (hasSendError) {
+      spanCount++
+    }
+    assertTraces(1) {
+      trace(0, spanCount) {
+        asyncServerSpan(it, 0, url, statusCode)
+        handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage)
+        if (hasSendError) {
+          sendErrorSpan(it, 2, span(1))
+        }
+      }
+    }
+
+    where:
+    desc         | action    | statusCode | bodyPredicate            | isCancelled | isError | errorMessage
+    "successful" | "succeed" | 200        | { it == "success" }      | false       | false   | null
+    "failing"    | "throw"   | 500        | { it == "failure" }      | false       | true    | "failure"
+    "canceled"   | "cancel"  | 503        | { it instanceof String } | true        | false   | null
+  }
+
+  @Unroll
+  def "should handle #desc CompletionStage (JAX-RS 2.1+ only)"() {
+    assumeTrue(shouldTestCompletableStageAsync())
+    given:
+    def url = address.resolve("async-completion-stage?action=${action}").toString()
+
+    when: "async call is started"
+    def futureResponse = client.get(url).aggregate()
+
+    then: "there are no traces yet"
+    assertTraces(0) {
+    }
+
+    when: "barrier is released and resource class sends response"
+    awaitBarrier(1, SECONDS)
+    def response = futureResponse.join()
+
+    then:
+    response.status().code() == statusCode
+    bodyPredicate(response.contentUtf8())
+
+    assertTraces(1) {
+      trace(0, 2) {
+        asyncServerSpan(it, 0, url, statusCode)
+        handlerSpan(it, 1, span(0), "jaxRs21Async", false, isError, errorMessage)
+      }
+    }
+
+    where:
+    desc         | action    | statusCode | bodyPredicate       | isError | errorMessage
+    "successful" | "succeed" | 200        | { it == "success" } | false   | null
+    "failing"    | "throw"   | 500        | { it == "failure" } | true    | "failure"
+  }
+
+  @Override
+  boolean hasHandlerSpan(ServerEndpoint endpoint) {
+    true
+  }
+
+  @Override
+  boolean testNotFound() {
+    false
+  }
+
+  @Override
+  boolean testPathParam() {
+    true
+  }
+
+  boolean testInterfaceMethodWithPath() {
+    true
+  }
+
+  boolean asyncCancelHasSendError() {
+    false
+  }
+
+  boolean shouldTestCompletableStageAsync() {
+    Boolean.getBoolean("testLatestDeps")
+  }
+
+  @Override
+  void serverSpan(TraceAssert trace,
+                  int index,
+                  String traceID = null,
+                  String parentID = null,
+                  String method = "GET",
+                  Long responseContentLength = null,
+                  ServerEndpoint endpoint = SUCCESS) {
+    serverSpan(trace, index, traceID, parentID, method,
+      endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path,
+      endpoint.resolve(address),
+      endpoint.status,
+      endpoint.query)
+  }
+
+  void asyncServerSpan(TraceAssert trace,
+                       int index,
+                       String url,
+                       int statusCode) {
+    def rawUrl = URI.create(url).toURL()
+    serverSpan(trace, index, null, null, "GET",
+      rawUrl.path,
+      rawUrl.toURI(),
+      statusCode,
+      null)
+  }
+
+  void serverSpan(TraceAssert trace,
+                  int index,
+                  String traceID,
+                  String parentID,
+                  String method,
+                  String path,
+                  URI fullUrl,
+                  int statusCode,
+                  String query) {
+    trace.span(index) {
+      name path
+      kind SERVER
+      if (statusCode >= 500) {
+        status ERROR
+      }
+      if (parentID != null) {
+        traceId traceID
+        parentSpanId parentID
+      } else {
+        hasNoParent()
+      }
+      attributes {
+        "$SemanticAttributes.NET_PEER_IP" { it == null || it == "127.0.0.1" } // Optional
+        "$SemanticAttributes.NET_PEER_PORT" Long
+        "$SemanticAttributes.HTTP_SCHEME" fullUrl.getScheme()
+        "$SemanticAttributes.HTTP_HOST" fullUrl.getHost() + ":" + fullUrl.getPort()
+        "$SemanticAttributes.HTTP_TARGET" fullUrl.getPath() + (fullUrl.getQuery() != null ? "?" + fullUrl.getQuery() : "")
+        "$SemanticAttributes.HTTP_METHOD" method
+        "$SemanticAttributes.HTTP_STATUS_CODE" statusCode
+        "$SemanticAttributes.HTTP_FLAVOR" "1.1"
+        "$SemanticAttributes.HTTP_USER_AGENT" TEST_USER_AGENT
+        "$SemanticAttributes.HTTP_CLIENT_IP" TEST_CLIENT_IP
+        "$SemanticAttributes.NET_TRANSPORT" IP_TCP
+        // Optional
+        "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long }
+        "$SemanticAttributes.HTTP_ROUTE" path
+        if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) {
+          "http.request.header.x_test_request" { it == ["test"] }
+          "http.response.header.x_test_response" { it == ["test"] }
+        }
+      }
+    }
+  }
+
+  @Override
+  void handlerSpan(TraceAssert trace,
+                   int index,
+                   Object parent,
+                   String method = "GET",
+                   ServerEndpoint endpoint = SUCCESS) {
+    handlerSpan(trace, index, parent,
+      endpoint.name().toLowerCase(),
+      false,
+      endpoint == EXCEPTION,
+      EXCEPTION.body)
+  }
+
+  void handlerSpan(TraceAssert trace,
+                   int index,
+                   Object parent,
+                   String methodName,
+                   boolean isCancelled,
+                   boolean isError,
+                   String exceptionMessage = null) {
+    trace.span(index) {
+      name "JaxRsTestResource.${methodName}"
+      kind INTERNAL
+      if (isError) {
+        status ERROR
+        errorEvent(Exception, exceptionMessage)
+      }
+      childOf((SpanData) parent)
+      attributes {
+        "$SemanticAttributes.CODE_NAMESPACE" "test.JaxRsTestResource"
+        "$SemanticAttributes.CODE_FUNCTION" methodName
+        if (isCancelled) {
+          "jaxrs.canceled" true
+        }
+      }
+    }
+  }
+}

+ 7 - 0
settings.gradle.kts

@@ -235,6 +235,8 @@ include(":instrumentation:java-http-client:javaagent")
 include(":instrumentation:java-util-logging:javaagent")
 include(":instrumentation:java-util-logging:shaded-stub-for-instrumenting")
 include(":instrumentation:jaxrs:jaxrs-common:bootstrap")
+include(":instrumentation:jaxrs:jaxrs-common:javaagent")
+include(":instrumentation:jaxrs:jaxrs-common:testing")
 include(":instrumentation:jaxrs:jaxrs-1.0:javaagent")
 include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-annotations:javaagent")
 include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-arquillian-testing")
@@ -248,6 +250,11 @@ include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent")
 include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:testing")
 include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-tomee-testing")
 include(":instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-wildfly-testing")
+include(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-annotations:javaagent")
+include(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:javaagent")
+include(":instrumentation:jaxrs:jaxrs-3.0:jaxrs-3.0-common:testing")
+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:javaagent")
 include(":instrumentation:jaxrs-client:jaxrs-client-2.0-testing")
 include(":instrumentation:jaxws:jaxws-2.0:javaagent")