Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ 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.
*
* @return A classloader containing only the libraries contained in the custom resources, with
* no 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.
* <p>
* 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: Mirth Corporation

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());
}
}
Loading