/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.module.artifact.classloader; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.rules.ExpectedException.none; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mule.runtime.module.artifact.classloader.ChildFirstLookupStrategy.CHILD_FIRST; import static org.mule.runtime.module.artifact.classloader.DefaultArtifactClassLoaderFilter.NULL_CLASSLOADER_FILTER; import static org.mule.runtime.module.artifact.classloader.ParentFirstLookupStrategy.PARENT_FIRST; import static org.mule.runtime.module.artifact.classloader.RegionClassLoader.REGION_OWNER_CANNOT_BE_REMOVED_ERROR; import static org.mule.runtime.module.artifact.classloader.RegionClassLoader.createCannotRemoveClassLoaderError; import static org.mule.runtime.module.artifact.classloader.RegionClassLoader.createClassLoaderAlreadyInRegionError; import static org.mule.runtime.module.artifact.classloader.RegionClassLoader.duplicatePackageMappingError; import static org.mule.runtime.module.artifact.classloader.RegionClassLoader.illegalPackageMappingError; import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptor; import org.mule.tck.junit4.AbstractMuleTestCase; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class RegionClassLoaderTestCase extends AbstractMuleTestCase { public static final String PACKAGE_NAME = "java.lang"; public static final String CLASS_NAME = PACKAGE_NAME + ".Object"; public static final Class PARENT_LOADED_CLASS = Object.class; public static final Class PLUGIN_LOADED_CLASS = String.class; public static final String RESOURCE_NAME = "dummy.txt"; public static final String APP_NAME = "testApp"; private static final String ARTIFACT_ID = "testAppId"; public final URL APP_LOADED_RESOURCE; public final URL PLUGIN_LOADED_RESOURCE; public final URL PARENT_LOADED_RESOURCE; private final TestApplicationClassLoader appClassLoader = new TestApplicationClassLoader(); private final TestArtifactClassLoader pluginClassLoader = new SubTestClassLoader(); private final ClassLoaderLookupPolicy lookupPolicy = mock(ClassLoaderLookupPolicy.class); private final ArtifactDescriptor artifactDescriptor; @Rule public ExpectedException expectedException = none(); public RegionClassLoaderTestCase() throws MalformedURLException { PARENT_LOADED_RESOURCE = new URL("file:///parent.txt"); APP_LOADED_RESOURCE = new URL("file:///app.txt"); PLUGIN_LOADED_RESOURCE = new URL("file:///plugin.txt"); artifactDescriptor = new ArtifactDescriptor(APP_NAME); } @Test(expected = ClassNotFoundException.class) public void failsToLoadClassWhenIsNotDefinedInAnyClassLoader() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.loadClass(CLASS_NAME)).thenThrow(new ClassNotFoundException()); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); List<ArtifactClassLoader> classLoaders = getClassLoaders(appClassLoader, pluginClassLoader); classLoaders.forEach(classLoader -> regionClassLoader.addClassLoader(classLoader, NULL_CLASSLOADER_FILTER)); when(lookupPolicy.getClassLookupStrategy(Object.class.getName())).thenReturn(CHILD_FIRST); regionClassLoader.loadClass(CLASS_NAME); } @Test public void loadsParentClassWhenIsNotDefinedInAnyRegionClassLoader() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.loadClass(CLASS_NAME)).thenReturn(PARENT_LOADED_CLASS); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); List<ArtifactClassLoader> classLoaders = getClassLoaders(appClassLoader, pluginClassLoader); classLoaders.forEach(classLoader -> regionClassLoader.addClassLoader(classLoader, NULL_CLASSLOADER_FILTER)); when(lookupPolicy.getClassLookupStrategy(Object.class.getName())).thenReturn(CHILD_FIRST); final Class loadedClass = regionClassLoader.loadClass(CLASS_NAME); assertThat(loadedClass, equalTo(PARENT_LOADED_CLASS)); } @Test public void loadsClassFromRegionMemberWhenPackageMappingDefined() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.loadClass(CLASS_NAME)).thenReturn(PARENT_LOADED_CLASS); when(lookupPolicy.getClassLookupStrategy(Object.class.getName())).thenReturn(CHILD_FIRST); when(lookupPolicy.getPackageLookupStrategy(PACKAGE_NAME)).thenReturn(CHILD_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(singleton(PACKAGE_NAME), emptySet())); pluginClassLoader.addClass(CLASS_NAME, PLUGIN_LOADED_CLASS); final Class loadedClass = regionClassLoader.loadClass(CLASS_NAME); assertThat(loadedClass, equalTo(PLUGIN_LOADED_CLASS)); } @Test public void returnsNullResourceWhenIsNotDefinedInAnyClassLoader() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(null); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); List<ArtifactClassLoader> classLoaders = getClassLoaders(appClassLoader, pluginClassLoader); classLoaders.forEach(classLoader -> regionClassLoader.addClassLoader(classLoader, NULL_CLASSLOADER_FILTER)); URL resource = regionClassLoader.getResource(RESOURCE_NAME); Assert.assertThat(resource, CoreMatchers.equalTo(null)); } @Test public void loadsResourceFromParentWhenIsNotDefinedInAnyRegionClassLoader() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); List<ArtifactClassLoader> classLoaders = getClassLoaders(appClassLoader, pluginClassLoader); classLoaders.forEach(classLoader -> regionClassLoader.addClassLoader(classLoader, NULL_CLASSLOADER_FILTER)); URL resource = regionClassLoader.getResource(RESOURCE_NAME); Assert.assertThat(resource, CoreMatchers.equalTo(PARENT_LOADED_RESOURCE)); } @Test public void loadsResourceFromRegionMemberWhenIsDefinedInRegionAndParentClassLoader() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); appClassLoader.addResource(RESOURCE_NAME, APP_LOADED_RESOURCE); regionClassLoader.addClassLoader(appClassLoader, new DefaultArtifactClassLoaderFilter(emptySet(), emptySet())); pluginClassLoader.addResource(RESOURCE_NAME, PLUGIN_LOADED_RESOURCE); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(emptySet(), singleton(RESOURCE_NAME))); URL resource = regionClassLoader.getResource(RESOURCE_NAME); Assert.assertThat(resource, CoreMatchers.equalTo(PLUGIN_LOADED_RESOURCE)); } @Test public void getsAllResources() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResources(RESOURCE_NAME)).thenReturn(new EnumerationAdapter<>(singleton(PARENT_LOADED_RESOURCE))); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); appClassLoader.addResource(RESOURCE_NAME, APP_LOADED_RESOURCE); regionClassLoader.addClassLoader(appClassLoader, new DefaultArtifactClassLoaderFilter(emptySet(), singleton(RESOURCE_NAME))); pluginClassLoader.addResource(RESOURCE_NAME, APP_LOADED_RESOURCE); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(emptySet(), singleton(RESOURCE_NAME))); final Enumeration<URL> resources = regionClassLoader.getResources(RESOURCE_NAME); List<URL> expectedResources = new LinkedList<>(); expectedResources.add(APP_LOADED_RESOURCE); expectedResources.add(PLUGIN_LOADED_RESOURCE); expectedResources.add(PARENT_LOADED_RESOURCE); Assert.assertThat(resources, EnumerationMatcher.equalTo(expectedResources)); } @Test public void disposesClassLoaders() throws Exception { when(lookupPolicy.getClassLookupStrategy(anyString())).thenReturn(PARENT_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, getClass().getClassLoader(), lookupPolicy); final ArtifactClassLoader regionMember1 = mock(ArtifactClassLoader.class, RETURNS_DEEP_STUBS); final ArtifactClassLoader regionMember2 = mock(ArtifactClassLoader.class, RETURNS_DEEP_STUBS); regionClassLoader.addClassLoader(regionMember1, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(regionMember2, NULL_CLASSLOADER_FILTER); regionClassLoader.dispose(); verify(regionMember1).dispose(); verify(regionMember2).dispose(); } @Test public void disposesClassLoadersEvenOnExceptions() throws Exception { when(lookupPolicy.getClassLookupStrategy(anyString())).thenReturn(PARENT_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, getClass().getClassLoader(), lookupPolicy); final ArtifactClassLoader regionMember1 = mock(ArtifactClassLoader.class, RETURNS_DEEP_STUBS); doThrow(new RuntimeException()).when(regionMember1).dispose(); final ArtifactClassLoader regionMember2 = mock(ArtifactClassLoader.class, RETURNS_DEEP_STUBS); regionClassLoader.addClassLoader(regionMember1, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(regionMember2, NULL_CLASSLOADER_FILTER); regionClassLoader.dispose(); verify(regionMember1).dispose(); verify(regionMember2).dispose(); } @Test public void verifiesThatClassLoaderIsNotAddedTwice() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(createClassLoaderAlreadyInRegionError(appClassLoader.getArtifactId())); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); } @Test public void failsToAddClassLoaderThatOverridesPackageMapping() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(lookupPolicy.getPackageLookupStrategy(anyString())).thenReturn(CHILD_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, new DefaultArtifactClassLoaderFilter(singleton(PACKAGE_NAME), emptySet())); expectedException.expect(IllegalStateException.class); expectedException.expectMessage(duplicatePackageMappingError(PACKAGE_NAME)); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(singleton(PACKAGE_NAME), emptySet())); } @Test public void failsToAddClassLoaderThatOverridesLookupPolicy() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(lookupPolicy.getPackageLookupStrategy(anyString())).thenReturn(PARENT_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); expectedException.expect(IllegalStateException.class); expectedException.expectMessage(illegalPackageMappingError(PACKAGE_NAME, PARENT_FIRST)); regionClassLoader.addClassLoader(appClassLoader, new DefaultArtifactClassLoaderFilter(singleton(PACKAGE_NAME), emptySet())); } @Test public void doesNotRemoveClassLoaderIfNotARegionMember() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); assertThat(regionClassLoader.removeClassLoader(appClassLoader), is(false)); } @Test public void removesClassLoaderMember() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(pluginClassLoader, NULL_CLASSLOADER_FILTER); assertThat(regionClassLoader.removeClassLoader(pluginClassLoader), is(true)); assertThat(regionClassLoader.getArtifactPluginClassLoaders(), is(empty())); } @Test public void failsToRemoveRegionOwner() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(REGION_OWNER_CANNOT_BE_REMOVED_ERROR); regionClassLoader.removeClassLoader(appClassLoader); } @Test public void failsToRemoveRegionMemberIfExportsPackages() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); when(lookupPolicy.getPackageLookupStrategy(anyString())).thenReturn(CHILD_FIRST); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(singleton("org.foo"), emptySet())); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(createCannotRemoveClassLoaderError(appClassLoader.getArtifactId())); regionClassLoader.removeClassLoader(pluginClassLoader); } @Test public void failsToRemoveRegionMemberIfExportsResources() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); when(parentClassLoader.getResource(RESOURCE_NAME)).thenReturn(PARENT_LOADED_RESOURCE); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(pluginClassLoader, new DefaultArtifactClassLoaderFilter(emptySet(), singleton("META-INF/pom.xml"))); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage(createCannotRemoveClassLoaderError(appClassLoader.getArtifactId())); regionClassLoader.removeClassLoader(pluginClassLoader); } @Test public void getsPluginsClassLoaders() throws Exception { final ClassLoader parentClassLoader = mock(ClassLoader.class); RegionClassLoader regionClassLoader = new RegionClassLoader(ARTIFACT_ID, artifactDescriptor, parentClassLoader, lookupPolicy); regionClassLoader.addClassLoader(appClassLoader, NULL_CLASSLOADER_FILTER); regionClassLoader.addClassLoader(pluginClassLoader, NULL_CLASSLOADER_FILTER); assertThat(regionClassLoader.getArtifactPluginClassLoaders(), contains(pluginClassLoader)); } private List<ArtifactClassLoader> getClassLoaders(ArtifactClassLoader... expectedClassLoaders) { List<ArtifactClassLoader> classLoaders = new LinkedList<>(); Collections.addAll(classLoaders, expectedClassLoaders); return classLoaders; } public static class TestApplicationClassLoader extends TestArtifactClassLoader { } // Used to ensure that the composite classloader is able to access // protected methods in subclasses by reflection public static class SubTestClassLoader extends TestArtifactClassLoader { } }