From dabc06b7158510794c9060b3669bde8903db4d47 Mon Sep 17 00:00:00 2001 From: Paul Hristea Date: Thu, 9 Jul 2026 01:34:44 +0300 Subject: [PATCH 1/4] Fallback isolated ClassLoader to platform classes by setting its parent to Platform ClassLoader Signed-off-by: Paul Hristea --- .../connect/server/util/javascript/MirthContextFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/mirth/connect/server/util/javascript/MirthContextFactory.java b/server/src/com/mirth/connect/server/util/javascript/MirthContextFactory.java index 5e303757d5..6ba3bb35f1 100644 --- a/server/src/com/mirth/connect/server/util/javascript/MirthContextFactory.java +++ b/server/src/com/mirth/connect/server/util/javascript/MirthContextFactory.java @@ -114,7 +114,7 @@ public ClassLoader getIsolatedClassLoader() { if (isolatedClassLoader == null && ArrayUtils.isNotEmpty(urls)) { synchronized (this) { if (isolatedClassLoader == null) { - isolatedClassLoader = new URLClassLoader(urls, null); + isolatedClassLoader = new URLClassLoader(urls, ClassLoader.getPlatformClassLoader()); } } } From 7e24227d2c63b14dc4697ea7726236d84cc1bdde Mon Sep 17 00:00:00 2001 From: Paul Hristea Date: Tue, 4 Aug 2026 11:20:41 +0300 Subject: [PATCH 2/4] Updated Javadoc to correctly explain Isolated ClassLoader behavior Signed-off-by: Paul Hristea --- .../com/mirth/connect/server/userutil/ContextFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java b/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java index c3aa5c0567..a633f9e893 100644 --- a/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java +++ b/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java @@ -52,11 +52,12 @@ public ClassLoader getClassLoader() { /** * Returns a classloader containing only the libraries contained in the custom resources, with - * no parent classloader. If no custom libraries are being used in the current JavaScript - * context, this will return null. + * {@code System.getPlatformClassLoader()} as parent classloader. If no custom libraries are being used + * in the current JavaScript context, this will return null. This classloader always loads classes + * parent-first, and the "Load Parent-First" option on Resources has no effect on it. * * @return A classloader containing only the libraries contained in the custom resources, with - * no parent classloader. + * {@code System.getPlatformClassLoader()} as parent classloader. */ public ClassLoader getIsolatedClassLoader() { return delegate.getIsolatedClassLoader(); From cf094d2ca6f459d6175036207ddfc6ee08eb9c14 Mon Sep 17 00:00:00 2001 From: Paul Hristea Date: Tue, 4 Aug 2026 20:37:58 +0300 Subject: [PATCH 3/4] Improve Javadoc wording for getIsolatedClassLoader Add JUnit test Signed-off-by: Paul Hristea --- .../server/userutil/ContextFactory.java | 19 ++-- .../javascript/MirthContextFactoryTest.java | 87 +++++++++++++++++++ 2 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java diff --git a/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java b/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java index a633f9e893..d7e2a90499 100644 --- a/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java +++ b/server/src/main/java/com/mirth/connect/server/userutil/ContextFactory.java @@ -51,13 +51,18 @@ public ClassLoader getClassLoader() { } /** - * Returns a classloader containing only the libraries contained in the custom resources, with - * {@code System.getPlatformClassLoader()} as parent classloader. If no custom libraries are being used - * in the current JavaScript context, this will return null. This classloader always loads classes - * parent-first, and the "Load Parent-First" option on Resources has no effect on it. - * - * @return A classloader containing only the libraries contained in the custom resources, with - * {@code System.getPlatformClassLoader()} as parent classloader. + * Returns a classloader containing only the libraries in the custom resources assigned to the + * current context. Use it to load classes from those libraries in isolation, for example a + * specific JDBC driver version, without interference from the versions the server itself ships. + *

+ * Core Java classes (for example {@code java.sql} or {@code javax.xml}) are visible through this + * classloader, but classes from the server or its plugins are not (its parent is + * {@link ClassLoader#getPlatformClassLoader()}). A class that exists in both a custom resource + * and the JDK resolves to the JDK's copy. The "Load Parent-First" option on a resource does not + * affect this classloader; it applies only to the classloader returned by {@link #getClassLoader()}. + * + * @return A classloader containing only the custom resource libraries, or null if the current + * context has no custom resources. */ public ClassLoader getIsolatedClassLoader() { return delegate.getIsolatedClassLoader(); diff --git a/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java b/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java new file mode 100644 index 0000000000..0cff5ffbd9 --- /dev/null +++ b/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: Open Integration Engine + +package com.mirth.connect.server.util.javascript; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.net.URL; +import java.util.HashSet; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.mirth.connect.server.builders.JavaScriptBuilder; +import com.mirth.connect.server.controllers.CodeTemplateController; +import com.mirth.connect.server.controllers.ConfigurationController; +import com.mirth.connect.server.controllers.ControllerFactory; +import com.mirth.connect.server.controllers.EventController; +import com.mirth.connect.server.controllers.ExtensionController; + +public class MirthContextFactoryTest { + + @BeforeClass + public static void setUpBeforeClass() { + // Same mocked ControllerFactory pattern as JavaScriptUtilTest, so this class is + // self-sufficient regardless of which test classes ran (and injected) before it. + ControllerFactory controllerFactory = mock(ControllerFactory.class); + + EventController eventController = mock(EventController.class); + when(controllerFactory.createEventController()).thenReturn(eventController); + + ConfigurationController configurationController = mock(ConfigurationController.class); + when(controllerFactory.createConfigurationController()).thenReturn(configurationController); + + ExtensionController extensionController = mock(ExtensionController.class); + when(controllerFactory.createExtensionController()).thenReturn(extensionController); + + CodeTemplateController codeTemplateController = mock(CodeTemplateController.class); + when(controllerFactory.createCodeTemplateController()).thenReturn(codeTemplateController); + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(ControllerFactory.class); + bind(ControllerFactory.class).toInstance(controllerFactory); + } + }); + injector.getInstance(ControllerFactory.class); + + JavaScriptBuilder.setControllersForTesting(extensionController, codeTemplateController); + } + + /* + * Regression test for #338: with a null parent, the isolated classloader cannot see java.sql + * on Java 9+, so custom driver resources failed to deploy. The parent must be the platform + * classloader: JRE classes visible, server classpath not. + */ + @Test + public void isolatedClassLoaderResolvesPlatformButNotServerClasses() throws Exception { + // This jar URL never gets read; it only exists because getIsolatedClassLoader() returns null on empty array. + URL dummyJar = new File("build/tmp/mirth-context-factory-test-dummy.jar").toURI().toURL(); + MirthContextFactory contextFactory = new MirthContextFactory(new URL[] { dummyJar }, new HashSet<>(), false); + + ClassLoader isolated = contextFactory.getIsolatedClassLoader(); + assertNotNull(isolated); + + // Fails with ClassNotFoundException if the parent ever goes back to null + isolated.loadClass("java.sql.Driver"); + + // Fails if the parent is ever widened to a loader that can see the server classpath + assertThrows(ClassNotFoundException.class, () -> isolated.loadClass(MirthContextFactory.class.getName())); + } + + @Test + public void isolatedClassLoaderIsNullWithoutResources() { + MirthContextFactory contextFactory = new MirthContextFactory(new URL[0], new HashSet<>(), false); + assertNull(contextFactory.getIsolatedClassLoader()); + } +} From cff4f7decf3e96ea09e420aff9093dd870c23bcd Mon Sep 17 00:00:00 2001 From: Paul Hristea Date: Wed, 5 Aug 2026 16:34:03 +0300 Subject: [PATCH 4/4] Alter copyright text Signed-off-by: Paul Hristea --- .../connect/server/util/javascript/MirthContextFactoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java b/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java index 0cff5ffbd9..b34ee8627d 100644 --- a/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java +++ b/server/src/test/java/com/mirth/connect/server/util/javascript/MirthContextFactoryTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: Open Integration Engine +// SPDX-FileCopyrightText: Mirth Corporation package com.mirth.connect.server.util.javascript;