diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java index 098066530d..1183ff28dc 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java @@ -58,15 +58,15 @@ public final class InstrumentationModuleInstaller { return parentAgentBuilder; } List helperClassNames = instrumentationModule.getMuzzleHelperClassNames(); - HelperResourceBuilderImpl helperResources = new HelperResourceBuilderImpl(); + HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl(); List helperResourceNames = instrumentationModule.helperResourceNames(); for (String helperResourceName : helperResourceNames) { - helperResources.register(helperResourceName); + helperResourceBuilder.register(helperResourceName); } - instrumentationModule.registerHelperResources(helperResources); + instrumentationModule.registerHelperResources(helperResourceBuilder); List typeInstrumentations = instrumentationModule.typeInstrumentations(); if (typeInstrumentations.isEmpty()) { - if (!helperClassNames.isEmpty() || !helperResources.getResourcePathMappings().isEmpty()) { + if (!helperClassNames.isEmpty() || !helperResourceBuilder.getResources().isEmpty()) { logger.warn( "Helper classes and resources won't be injected if no types are instrumented: {}", instrumentationModule.instrumentationName()); @@ -82,7 +82,7 @@ public final class InstrumentationModuleInstaller { new HelperInjector( instrumentationModule.instrumentationName(), helperClassNames, - helperResources.getResourcePathMappings(), + helperResourceBuilder.getResources(), Utils.getExtensionsClassLoader(), instrumentation); InstrumentationContextProvider contextProvider = diff --git a/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/HelperInjectionTest.groovy b/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/HelperInjectionTest.groovy index 39d1b013f8..23f50e39ba 100644 --- a/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/HelperInjectionTest.groovy +++ b/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/HelperInjectionTest.groovy @@ -28,7 +28,7 @@ class HelperInjectionTest extends Specification { ClassLoader helpersSourceLoader = new URLClassLoader(helpersSourceUrls) String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass' - HelperInjector injector = new HelperInjector("test", [helperClassName], [:], helpersSourceLoader, null) + HelperInjector injector = new HelperInjector("test", [helperClassName], [], helpersSourceLoader, null) AtomicReference emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null)) when: @@ -60,7 +60,7 @@ class HelperInjectionTest extends Specification { ByteBuddyAgent.install() AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation()) String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass' - HelperInjector injector = new HelperInjector("test", [helperClassName], [:], this.class.classLoader, ByteBuddyAgent.getInstrumentation()) + HelperInjector injector = new HelperInjector("test", [helperClassName], [], this.class.classLoader, ByteBuddyAgent.getInstrumentation()) URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null) when: diff --git a/muzzle/build.gradle.kts b/muzzle/build.gradle.kts index e42ac571ca..0069eb04b1 100644 --- a/muzzle/build.gradle.kts +++ b/muzzle/build.gradle.kts @@ -9,6 +9,9 @@ dependencies { // Only used during compilation by bytebuddy plugin compileOnly("com.google.guava:guava") + compileOnly("com.google.auto.value:auto-value-annotations") + annotationProcessor("com.google.auto.value:auto-value") + api("net.bytebuddy:byte-buddy") implementation(project(":javaagent-bootstrap")) diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index a2cf19badb..60f8dcde76 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.tooling; import io.opentelemetry.instrumentation.api.caching.Cache; import io.opentelemetry.javaagent.bootstrap.HelperResources; +import io.opentelemetry.javaagent.tooling.muzzle.HelperResource; import java.io.File; import java.io.IOException; import java.lang.instrument.Instrumentation; @@ -63,7 +64,7 @@ public class HelperInjector implements Transformer { private final String requestingName; private final Set helperClassNames; - private final Map helperResourcePathMappings; + private final List helperResources; @Nullable private final ClassLoader helpersSource; @Nullable private final Instrumentation instrumentation; private final Map dynamicTypeMap = new LinkedHashMap<>(); @@ -87,14 +88,14 @@ public class HelperInjector implements Transformer { public HelperInjector( String requestingName, List helperClassNames, - Map helperResourcePathMappings, + List helperResources, // TODO can this be replaced with the context classloader? ClassLoader helpersSource, Instrumentation instrumentation) { this.requestingName = requestingName; this.helperClassNames = new LinkedHashSet<>(helperClassNames); - this.helperResourcePathMappings = helperResourcePathMappings; + this.helperResources = helperResources; this.helpersSource = helpersSource; this.instrumentation = instrumentation; } @@ -106,7 +107,7 @@ public class HelperInjector implements Transformer { this.helperClassNames = helperMap.keySet(); this.dynamicTypeMap.putAll(helperMap); - this.helperResourcePathMappings = Collections.emptyMap(); + this.helperResources = Collections.emptyList(); this.helpersSource = null; this.instrumentation = instrumentation; } @@ -149,19 +150,20 @@ public class HelperInjector implements Transformer { classLoader = injectHelperClasses(typeDescription, classLoader, module); } - if (helpersSource != null && !helperResourcePathMappings.isEmpty()) { - for (Map.Entry entry : helperResourcePathMappings.entrySet()) { - String agentResourcePath = entry.getValue(); - String applicationResourcePath = entry.getKey(); - URL resource = helpersSource.getResource(agentResourcePath); + if (helpersSource != null && !helperResources.isEmpty()) { + for (HelperResource helperResource : helperResources) { + URL resource = helpersSource.getResource(helperResource.getAgentPath()); if (resource == null) { - logger.debug("Helper resource {} requested but not found.", agentResourcePath); + logger.debug( + "Helper resource {} requested but not found.", helperResource.getAgentPath()); continue; } logger.debug( - "Injecting resource onto classloader {} -> {}", classLoader, applicationResourcePath); - HelperResources.register(classLoader, applicationResourcePath, resource); + "Injecting resource onto classloader {} -> {}", + classLoader, + helperResource.getApplicationPath()); + HelperResources.register(classLoader, helperResource.getApplicationPath(), resource); } } diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ClassLoaderMatcher.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ClassLoaderMatcher.java index 5ae85e9aa5..dc5cc920e8 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ClassLoaderMatcher.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ClassLoaderMatcher.java @@ -82,17 +82,17 @@ public class ClassLoaderMatcher { try { // verify helper injector works List allHelperClasses = instrumentationModule.getMuzzleHelperClassNames(); - HelperResourceBuilderImpl helperResources = new HelperResourceBuilderImpl(); + HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl(); List helperResourceNames = instrumentationModule.helperResourceNames(); for (String helperResourceName : helperResourceNames) { - helperResources.register(helperResourceName); + helperResourceBuilder.register(helperResourceName); } - instrumentationModule.registerHelperResources(helperResources); + instrumentationModule.registerHelperResources(helperResourceBuilder); if (!allHelperClasses.isEmpty()) { new HelperInjector( instrumentationModule.instrumentationName(), allHelperClasses, - helperResources.getResourcePathMappings(), + helperResourceBuilder.getResources(), Thread.currentThread().getContextClassLoader(), null) .transform(null, null, classLoader, null); diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResource.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResource.java new file mode 100644 index 0000000000..50aba1e5ce --- /dev/null +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResource.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.muzzle; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class HelperResource { + + /** + * Create a new helper resource object. + * + * @param applicationPath The path in the user's class loader at which to inject the resource. + * @param agentPath The path in the agent class loader from which to get the content for the + * resource. + */ + public static HelperResource create(String applicationPath, String agentPath) { + return new AutoValue_HelperResource(applicationPath, agentPath); + } + + /** The path in the user's class loader at which to inject the resource. */ + public abstract String getApplicationPath(); + + /** The path in the agent class loader from which to get the content for the resource. */ + public abstract String getAgentPath(); +} diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResourceBuilderImpl.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResourceBuilderImpl.java index e5daa15f80..9ffecfa4d3 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResourceBuilderImpl.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/HelperResourceBuilderImpl.java @@ -6,30 +6,24 @@ package io.opentelemetry.javaagent.tooling.muzzle; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; public class HelperResourceBuilderImpl implements HelperResourceBuilder { - private final Map resourcePathMappings = new HashMap<>(); + private final List resources = new ArrayList<>(); @Override public void register(String resourcePath) { - resourcePathMappings.put(resourcePath, resourcePath); + resources.add(HelperResource.create(resourcePath, resourcePath)); } @Override public void register(String applicationResourcePath, String agentResourcePath) { - resourcePathMappings.put(applicationResourcePath, agentResourcePath); + resources.add(HelperResource.create(applicationResourcePath, agentResourcePath)); } - /** - * Returns the registered mappings, where the keys are the paths in the user's class loader at - * which to inject the resource ({@code applicationResourcePath}) and the values are the paths in - * the agent class loader from which to get the content for the resource ({@code - * agentResourcePath}). - */ - public Map getResourcePathMappings() { - return resourcePathMappings; + public List getResources() { + return resources; } } diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollector.java index 276da04e18..a0c418bd53 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollector.java @@ -74,14 +74,28 @@ public final class ReferenceCollector { * * @param resource path to the resource file, same as in {@link ClassLoader#getResource(String)} * @see InstrumentationClassPredicate + * @deprecated Use {@link #collectReferencesFromResource(HelperResource)} instead. */ + @Deprecated public void collectReferencesFromResource(String resource) { - if (!isSpiFile(resource)) { + collectReferencesFromResource(HelperResource.create(resource, resource)); + } + + /** + * If passed {@code resource} path points to an SPI file (either Java {@link + * java.util.ServiceLoader} or AWS SDK {@code ExecutionInterceptor}) reads the file and adds every + * implementation as a reference, traversing the graph of classes until a non-instrumentation + * (external) class is encountered. + * + * @see InstrumentationClassPredicate + */ + public void collectReferencesFromResource(HelperResource helperResource) { + if (!isSpiFile(helperResource.getApplicationPath())) { return; } List spiImplementations = new ArrayList<>(); - try (InputStream stream = getResourceStream(resource)) { + try (InputStream stream = getResourceStream(helperResource.getAgentPath())) { BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8)); while (reader.ready()) { String line = reader.readLine(); @@ -90,7 +104,7 @@ public final class ReferenceCollector { } } } catch (IOException e) { - throw new IllegalStateException("Error reading resource " + resource, e); + throw new IllegalStateException("Error reading resource " + helperResource.getAgentPath(), e); } visitClassesAndCollectReferences(spiImplementations, /* startsFromAdviceClass= */ false);