+ * IMPORTANT: This class is relocated in a different package and stored as a classpath resource to be injected into bootstrap classloader.
+ * A copy of this resource is stored in 'src/main/resources' and should be updated by running 'mvn clean package' whenever
+ * this class is being modified. This has only an effect when running code/tests in the IDE as the resources are loaded
+ * from the project classpath and not the packaged artifact.
+ *
+ */
+@SuppressWarnings("unused")
public class IndyBootstrapDispatcher {
+
public static Method bootstrap;
private static final MethodHandle VOID_NOOP;
diff --git a/apm-indy-bootstrap-module/src/main/java/co/elastic/apm/agent/bci/IndyBootstrapDispatcherModuleSetter.java b/apm-agent-bootstrap/src/main/java/bootstrap/modulesetter/ModuleSetter.java
similarity index 69%
rename from apm-indy-bootstrap-module/src/main/java/co/elastic/apm/agent/bci/IndyBootstrapDispatcherModuleSetter.java
rename to apm-agent-bootstrap/src/main/java/bootstrap/modulesetter/ModuleSetter.java
index f0a01e49a8..80573f0148 100644
--- a/apm-indy-bootstrap-module/src/main/java/co/elastic/apm/agent/bci/IndyBootstrapDispatcherModuleSetter.java
+++ b/apm-agent-bootstrap/src/main/java/bootstrap/modulesetter/ModuleSetter.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package co.elastic.apm.agent.bci;
+package bootstrap.modulesetter;
import sun.misc.Unsafe;
@@ -27,16 +27,25 @@
*
* NOTE: This class is compiled with Java 9 so it must only be loaded though reflection and only when running on Java 9.
* In addition, since it relies on the {@link Unsafe} API, it must be loaded by the bootstrap or platform class loaders.
+ *
+ *
+ * IMPORTANT: This class is relocated in a different package and stored as a classpath resource to be injected into bootstrap classloader.
+ * A copy of this resource is stored in 'src/main/resources' and should be updated by running 'mvn clean package' whenever
+ * this class is being modified. This has only an effect when running code/tests in the IDE as the resources are loaded
+ * from the project classpath and not the packaged artifact.
+ *
*/
+
@SuppressWarnings("unused")
-public class IndyBootstrapDispatcherModuleSetter {
+public class ModuleSetter {
+
+ public static void setJavaBaseModule(Class> targetClass) throws Exception {
- public static void setJavaBaseModule(Class> indyBootstrapDispatcherClass) throws Exception {
Field moduleField = Class.class.getDeclaredField("module");
if (moduleField.getType() == Module.class) {
Module javaBaseModule = Class.class.getModule();
Unsafe unsafe = Unsafe.getUnsafe();
- unsafe.putObject(indyBootstrapDispatcherClass, unsafe.objectFieldOffset(moduleField), javaBaseModule);
+ unsafe.putObject(targetClass, unsafe.objectFieldOffset(moduleField), javaBaseModule);
} else {
throw new IllegalStateException("Unexpected module field type: " + moduleField.getType().getName());
}
diff --git a/apm-agent-bootstrap/src/main/resources/bootstrap/co/elastic/apm/agent/modulesetter/ModuleSetter.esclazz b/apm-agent-bootstrap/src/main/resources/bootstrap/co/elastic/apm/agent/modulesetter/ModuleSetter.esclazz
new file mode 100644
index 0000000000..b73dbacd2f
Binary files /dev/null and b/apm-agent-bootstrap/src/main/resources/bootstrap/co/elastic/apm/agent/modulesetter/ModuleSetter.esclazz differ
diff --git a/apm-agent-bootstrap/src/main/resources/bootstrap/java/lang/IndyBootstrapDispatcher.esclazz b/apm-agent-bootstrap/src/main/resources/bootstrap/java/lang/IndyBootstrapDispatcher.esclazz
new file mode 100644
index 0000000000..544cd281f8
Binary files /dev/null and b/apm-agent-bootstrap/src/main/resources/bootstrap/java/lang/IndyBootstrapDispatcher.esclazz differ
diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml
index d8609e4e2c..ae19d57920 100644
--- a/apm-agent-core/pom.xml
+++ b/apm-agent-core/pom.xml
@@ -93,10 +93,11 @@
${project.groupId}
- apm-indy-bootstrap-module
+ apm-agent-bootstrap${project.version}
- test
+ runtime
+
com.squareup.okhttp3okhttp
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index 5902a50ed0..efb2472fb2 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -168,19 +168,28 @@
public class IndyBootstrap {
/**
- * Starts with {@code java.lang} so that OSGi class loaders don't restrict access to it
+ * Starts with {@code java.lang} so that OSGi class loaders don't restrict access to it.
+ * This also allows to load it in {@code java.base} module on Java9+ for Hotspot, Open J9 requires {@code ModuleSetter}
*/
private static final String INDY_BOOTSTRAP_CLASS_NAME = "java.lang.IndyBootstrapDispatcher";
+
+ /**
+ * The class file of {@code IndyBootstrapDispatcher}, loaded from classpath resource, {@code esclazz} extension avoids
+ * being loaded as a regular class.
+ */
+ private static final String INDY_BOOTSTRAP_RESOURCE = "bootstrap/java/lang/IndyBootstrapDispatcher.esclazz";
+
/**
* Needs to be loaded from the bootstrap CL because it uses {@code sun.misc.Unsafe}.
* In addition, needs to be loaded explicitly by name only when running on Java 9, because compiled with Java 9
*/
- private static final String INDY_BOOTSTRAP_MODULE_SETTER_CLASS_NAME = "co.elastic.apm.agent.bci.IndyBootstrapDispatcherModuleSetter";
+ private static final String INDY_BOOTSTRAP_MODULE_SETTER_CLASS_NAME = "co.elastic.apm.agent.modulesetter.ModuleSetter";
+
/**
- * The class file of {@code java.lang.IndyBootstrapDispatcher}.
- * Ends with {@code clazz} because if it ended with {@code class}, it would be loaded like a regular class.
+ * The class file of {@code ModuleSetter}, loaded from classpath resource, {@code esclazz} extension avoids being
+ * loaded as a regular class.
*/
- private static final String INDY_BOOTSTRAP_RESOURCE = "bootstrap/IndyBootstrapDispatcher.clazz";
+ private static final String INDY_BOOTSTRAP_MODULE_SETTER_RESOURCE = "bootstrap/co/elastic/apm/agent/modulesetter/ModuleSetter.esclazz";
/**
* The name of the class we use as the lookup class during the invokedynamic bootstrap flow. The bytecode of this
@@ -220,17 +229,7 @@ public static Method getIndyBootstrapMethod(final Logger logger) {
* Injects the {@code java.lang.IndyBootstrapDispatcher} class into the bootstrap class loader if it wasn't already.
*/
private static Class> initIndyBootstrap(final Logger logger) throws Exception {
- Class> indyBootstrapDispatcherClass;
- try {
- indyBootstrapDispatcherClass = Class.forName(INDY_BOOTSTRAP_CLASS_NAME, false, null);
- } catch (ClassNotFoundException e) {
- byte[] bootstrapClass = IOUtils.readToBytes(ElasticApmAgent.getAgentClassLoader().getResourceAsStream(INDY_BOOTSTRAP_RESOURCE));
- if (bootstrapClass == null || bootstrapClass.length == 0) {
- throw new IllegalStateException("Could not locate " + INDY_BOOTSTRAP_RESOURCE);
- }
- ClassInjector.UsingUnsafe.ofBootLoader().injectRaw(Collections.singletonMap(INDY_BOOTSTRAP_CLASS_NAME, bootstrapClass));
- indyBootstrapDispatcherClass = Class.forName(INDY_BOOTSTRAP_CLASS_NAME, false, null);
- }
+ Class> indyBootstrapDispatcherClass = loadClassInBootstrap(INDY_BOOTSTRAP_CLASS_NAME, INDY_BOOTSTRAP_RESOURCE);
if (JvmRuntimeInfo.ofCurrentVM().getMajorVersion() >= 9 && JvmRuntimeInfo.ofCurrentVM().isJ9VM()) {
try {
@@ -243,6 +242,42 @@ private static Class> initIndyBootstrap(final Logger logger) throws Exception
return indyBootstrapDispatcherClass;
}
+ /**
+ * Loads a class from classpath resource in bootstrap classloader.
+ *
+ * Ensuring that classes loaded through this method can ONLY be loaded in the bootstrap CL requires the following:
+ *
+ *
The class bytecode resource name should not end with the {@code .class} suffix
+ *
The class bytecode resource name should be in a location that reflects its package
+ *
For tests in IDE, the class name used here should be distinct from its original class name to ensure
+ * that only the relocated resource is being used
+ *
+ *
+ * @param className class name
+ * @param resourceName class resource name
+ * @return class loaded in bootstrap classloader
+ * @throws IOException if unable to open provided resource
+ * @throws ClassNotFoundException if unable to load class in bootstrap CL
+ */
+ private static Class> loadClassInBootstrap(String className, String resourceName) throws IOException, ClassNotFoundException {
+ Class> bootstrapClass;
+ try {
+ // Will return non-null value only if the class has already been loaded.
+ // Ensuring that a class can ONLY be loaded through this method and not from regular classloading relies
+ // on applying the listed instructions in method documentation
+ bootstrapClass = Class.forName(className, false, null);
+ } catch (ClassNotFoundException e) {
+ byte[] classBytes = IOUtils.readToBytes(ElasticApmAgent.getAgentClassLoader().getResourceAsStream(resourceName));
+ if (classBytes == null || classBytes.length == 0) {
+ throw new IllegalStateException("Could not locate " + resourceName);
+ }
+ ClassInjector.UsingUnsafe.ofBootLoader().injectRaw(Collections.singletonMap(className, classBytes));
+ bootstrapClass = Class.forName(className, false, null);
+ }
+ return bootstrapClass;
+ }
+
+
/**
* A package-private method for unit-testing of the module overriding functionality
*
@@ -252,7 +287,8 @@ private static Class> initIndyBootstrap(final Logger logger) throws Exception
static void setJavaBaseModule(Class> targetClass) throws Throwable {
// In order to override the original unnamed module assigned to the IndyBootstrapDispatcher, we rely on the
// Unsafe API, which requires the caller to be loaded by the Bootstrap CL
- Class> moduleSetterClass = Class.forName(INDY_BOOTSTRAP_MODULE_SETTER_CLASS_NAME, false, null);
+
+ Class> moduleSetterClass = loadClassInBootstrap(INDY_BOOTSTRAP_MODULE_SETTER_CLASS_NAME, INDY_BOOTSTRAP_MODULE_SETTER_RESOURCE);
MethodHandles.lookup()
.findStatic(moduleSetterClass, "setJavaBaseModule", MethodType.methodType(void.class, Class.class))
.invoke(targetClass);
diff --git a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz
deleted file mode 100644
index 4528ca7da9..0000000000
Binary files a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz and /dev/null differ
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/IndyBootstrapTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/IndyBootstrapTest.java
index d23d39741a..5139cf7014 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/IndyBootstrapTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/IndyBootstrapTest.java
@@ -19,12 +19,7 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.AbstractInstrumentationTest;
-import net.bytebuddy.dynamic.loading.ClassInjector;
import org.junit.jupiter.api.Test;
-import org.stagemonitor.util.IOUtils;
-
-import java.io.InputStream;
-import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,12 +30,6 @@ void testSetJavaBaseModule() throws Throwable {
Module javaBaseModule = Class.class.getModule();
assertThat(IndyBootstrapTest.class.getModule()).isNotEqualTo(javaBaseModule);
- // In order to test this functionality, IndyBootstrapDispatcherModuleSetter needs to be loaded from the Boot CL.
- // We don't mind loading it with the test's class loader as well only to get it's class file
- InputStream classFileAsStream = IndyBootstrapDispatcherModuleSetter.class.getResourceAsStream("IndyBootstrapDispatcherModuleSetter.class");
- byte[] bootstrapClass = IOUtils.readToBytes(classFileAsStream);
- ClassInjector.UsingUnsafe.ofBootLoader().injectRaw(Collections.singletonMap(IndyBootstrapDispatcherModuleSetter.class.getName(), bootstrapClass));
-
IndyBootstrap.setJavaBaseModule(IndyBootstrapTest.class);
assertThat(IndyBootstrapTest.class.getModule()).isEqualTo(javaBaseModule);
}
diff --git a/apm-agent/pom.xml b/apm-agent/pom.xml
index 25d35b8025..76dfcd2f77 100644
--- a/apm-agent/pom.xml
+++ b/apm-agent/pom.xml
@@ -36,7 +36,7 @@
${project.groupId}
- apm-indy-bootstrap-module
+ apm-agent-bootstrap${project.version}
diff --git a/apm-indy-bootstrap-module/pom.xml b/apm-indy-bootstrap-module/pom.xml
deleted file mode 100644
index feefee45d3..0000000000
--- a/apm-indy-bootstrap-module/pom.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- 4.0.0
-
-
- apm-agent-parent
- co.elastic.apm
- 1.28.2-SNAPSHOT
-
-
- apm-indy-bootstrap-module
- ${project.groupId}:${project.artifactId}
-
-
- true
-
- ${project.basedir}/..
-
- 9
-
-
- ${maven.compiler.target}
-
-
- true
-
-
diff --git a/elastic-apm-agent/pom.xml b/elastic-apm-agent/pom.xml
index 993e018fd1..0c6d6d6d51 100644
--- a/elastic-apm-agent/pom.xml
+++ b/elastic-apm-agent/pom.xml
@@ -106,7 +106,6 @@
maven-antrun-plugin
- 3.0.0shade-cached-lookup-key
diff --git a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/TomcatIT.java b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/TomcatIT.java
index c48a5ee0bf..7ff8a75947 100644
--- a/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/TomcatIT.java
+++ b/integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/TomcatIT.java
@@ -48,9 +48,8 @@ public static Iterable
+
+ maven-antrun-plugin
+ 3.0.0
+