Copy instrumentation to jax-rs2
MODIFY
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsDecorator.java
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsDecorator.java
@@ -3,6 +3,7 @@ package datadog.trace.instrumentation.jaxrs2;
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
import datadog.trace.agent.decorator.BaseDecorator;
+import datadog.trace.agent.tooling.ClassHierarchyIterable;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import datadog.trace.bootstrap.WeakMap;
@@ -10,8 +11,6 @@ import datadog.trace.instrumentation.api.AgentSpan;
import datadog.trace.instrumentation.api.Tags;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.ws.rs.HttpMethod;
@@ -83,9 +82,31 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
String resourceName = classMap.get(method);
if (resourceName == null) {
- final String httpMethod = locateHttpMethod(method);
- final List<Path> paths = gatherPaths(target, method);
- resourceName = buildResourceName(httpMethod, paths);
+ String httpMethod = null;
+ Path methodPath = null;
+ final Path classPath = findClassPath(target);
+ for (final Class currentClass : new ClassHierarchyIterable(target)) {
+ final 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;
+ }
+ }
+ }
+ resourceName = buildResourceName(httpMethod, classPath, methodPath);
classMap.put(method, resourceName);
}
@@ -102,40 +123,77 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
return httpMethod;
}
- private List<Path> gatherPaths(Class<Object> target, final Method method) {
- final List<Path> paths = new ArrayList();
- while (target != null && target != Object.class) {
- final Path annotation = target.getAnnotation(Path.class);
+ private Path findMethodPath(final Method method) {
+ return method.getAnnotation(Path.class);
+ }
+
+ private Path findClassPath(final Class<Object> target) {
+ for (final Class<?> currentClass : new ClassHierarchyIterable(target)) {
+ final Path annotation = currentClass.getAnnotation(Path.class);
if (annotation != null) {
- paths.add(annotation);
- break; // Annotation overridden, no need to continue.
+ // Annotation overridden, no need to continue.
+ return annotation;
}
- target = target.getSuperclass();
}
- final Path methodPath = method.getAnnotation(Path.class);
- if (methodPath != null) {
- paths.add(methodPath);
+
+ return null;
}
- return paths;
+
+ private Method findMatchingMethod(final Method baseMethod, final Method[] methods) {
+ nextMethod:
+ for (final Method method : methods) {
+ if (!baseMethod.getReturnType().equals(method.getReturnType())) {
+ continue;
}
- private String buildResourceName(final String httpMethod, final List<Path> paths) {
+ if (!baseMethod.getName().equals(method.getName())) {
+ continue;
+ }
+
+ final Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
+ final 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 String buildResourceName(
+ final String httpMethod, final Path classPath, final Path methodPath) {
final String resourceName;
final StringBuilder resourceNameBuilder = new StringBuilder();
if (httpMethod != null) {
resourceNameBuilder.append(httpMethod);
resourceNameBuilder.append(" ");
}
- Path last = null;
- for (final Path path : paths) {
- if (path.value().startsWith("/") || (last != null && last.value().endsWith("/"))) {
- resourceNameBuilder.append(path.value());
- } else {
+ boolean skipSlash = false;
+ if (classPath != null) {
+ if (!classPath.value().startsWith("/")) {
+ resourceNameBuilder.append("/");
+ }
+ resourceNameBuilder.append(classPath.value());
+ skipSlash = classPath.value().endsWith("/");
+ }
+
+ if (methodPath != null) {
+ String path = methodPath.value();
+ if (skipSlash) {
+ if (path.startsWith("/")) {
+ path = path.length() == 1 ? "" : path.substring(1);
+ }
+ } else if (!path.startsWith("/")) {
resourceNameBuilder.append("/");
- resourceNameBuilder.append(path.value());
}
- last = path;
+ resourceNameBuilder.append(path);
}
+
resourceName = resourceNameBuilder.toString().trim();
return resourceName;
}

MODIFY
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsInstrumentation.java
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsInstrumentation.java
package datadog.trace.instrumentation.jaxrs2;
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.hasSuperMethod;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
@@ -8,6 +9,7 @@ import static datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator.DEC
import static java.util.Collections.singletonMap;
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 com.google.auto.service.AutoService;
@@ -47,13 +49,19 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
@Override
public String[] helperClassNames() {
return new String[] {
- "datadog.trace.agent.decorator.BaseDecorator", packageName + ".JaxRsAnnotationsDecorator",
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
+ packageName + ".JaxRsAnnotationsDecorator",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
+ isMethod()
+ .and(
+ hasSuperMethod(
isAnnotatedWith(
named("javax.ws.rs.Path")
.or(named("javax.ws.rs.DELETE"))
@@ -61,7 +69,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
.or(named("javax.ws.rs.HEAD"))
.or(named("javax.ws.rs.OPTIONS"))
.or(named("javax.ws.rs.POST"))
- .or(named("javax.ws.rs.PUT"))),
+ .or(named("javax.ws.rs.PUT"))))),
JaxRsAnnotationsAdvice.class.getName());
}

MODIFY
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/groovy/JaxRsAnnotationsInstrumentationTest.groovy
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/groovy/JaxRsAnnotationsInstrumentationTest.groovy
@@ -2,6 +2,7 @@ import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.bootstrap.WeakMap
import datadog.trace.instrumentation.api.Tags
import datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator
+
import javax.ws.rs.DELETE
import javax.ws.rs.GET
import javax.ws.rs.HEAD
@@ -9,7 +10,6 @@ import javax.ws.rs.OPTIONS
import javax.ws.rs.POST
import javax.ws.rs.PUT
import javax.ws.rs.Path
-
import java.lang.reflect.Method
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
@@ -98,13 +98,13 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
void call() {
}
}
- "POST /c" | new InterfaceWithPath() {
+ "POST /interface/c" | new InterfaceWithPath() {
@POST
@Path("/c")
void call() {
}
}
- "HEAD" | new InterfaceWithPath() {
+ "HEAD /interface" | new InterfaceWithPath() {
@HEAD
void call() {
}
@@ -126,7 +126,7 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
void call() {
}
}
- "DELETE /child" | new ChildClassWithPath() {
+ "DELETE /child/call" | new ChildClassWithPath() {
@DELETE
void call() {
}
@@ -168,18 +168,6 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
void call() {
}
} | _
- new InterfaceWithPath() {
- void call() {
- }
- } | _
- new AbstractClassWithPath() {
- void call() {
- }
- } | _
- new ChildClassWithPath() {
- void call() {
- }
- } | _
}
interface Jax {

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/groovy/JerseyTest.groovy
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.api.DDSpanTypes
+import datadog.trace.instrumentation.api.Tags
+import io.dropwizard.testing.junit.ResourceTestRule
+import org.junit.ClassRule
+import spock.lang.Shared
+
+import javax.ws.rs.client.Entity
+
+import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
+
+class JerseyTest extends AgentTestRunner {
+
+ @Shared
+ @ClassRule
+ ResourceTestRule resources = ResourceTestRule.builder()
+ .addResource(new Resource.Test1())
+ .addResource(new Resource.Test2())
+ .addResource(new Resource.Test3())
+ .build()
+
+ def "test #resource"() {
+ when:
+ // start a trace because the test doesn't go through any servlet or other instrumentation.
+ def response = runUnderTrace("test.span") {
+ resources.client().target(resource).request().post(Entity.text(""), String)
+ }
+
+ then:
+ response == expectedResponse
+
+ assertTraces(1) {
+ trace(0, 2) {
+ span(0) {
+ operationName "test.span"
+ resourceName expectedResourceName
+ tags {
+ "$Tags.COMPONENT" "jax-rs"
+ defaultTags()
+ }
+ }
+
+ span(1) {
+ childOf span(0)
+ operationName "jax-rs.request"
+ resourceName controllerName
+ spanType DDSpanTypes.HTTP_SERVER
+ tags {
+ "$Tags.COMPONENT" "jax-rs-controller"
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ resource | expectedResourceName | controllerName | expectedResponse
+ "/test/hello/bob" | "POST /test/hello/{name}" | "Test1.hello" | "Test1 bob!"
+ "/test2/hello/bob" | "POST /test2/hello/{name}" | "Test2.hello" | "Test2 bob!"
+ "/test3/hi/bob" | "POST /test3/hi/{name}" | "Test3.hello" | "Test3 bob!"
+ }
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/java/Resource.java
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+// Originally had this as a groovy class but was getting some weird errors.
+@Path("/ignored")
+public interface Resource {
+ @Path("ignored")
+ String hello(final String name);
+
+ @Path("/test")
+ interface SubResource extends Cloneable, Resource {
+ @Override
+ @POST
+ @Path("/hello/{name}")
+ String hello(@PathParam("name") final String name);
+ }
+
+ class Test1 implements SubResource {
+ @Override
+ public String hello(final String name) {
+ return "Test1 " + name + "!";
+ }
+ }
+
+ @Path("/test2")
+ class Test2 implements SubResource {
+ @Override
+ public String hello(final String name) {
+ return "Test2 " + name + "!";
+ }
+ }
+
+ @Path("/test3")
+ class Test3 implements SubResource {
+ @Override
+ @POST
+ @Path("/hi/{name}")
+ public String hello(@PathParam("name") final String name) {
+ return "Test3 " + name + "!";
+ }
+ }
+}

Copy instrumentation to jax-rs2