/* * 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.test.runner.classloader; import static org.mule.runtime.core.util.ClassUtils.withContextClassLoader; import org.mule.runtime.container.api.ModuleRepository; import org.mule.runtime.container.api.MuleModule; import org.mule.runtime.container.internal.ContainerClassLoaderFactory; import org.mule.runtime.container.internal.ContainerModuleDiscoverer; import org.mule.runtime.container.internal.DefaultModuleRepository; import org.mule.runtime.container.internal.JreModuleDiscoverer; import org.mule.runtime.container.internal.MuleClassLoaderLookupPolicy; import org.mule.runtime.module.artifact.classloader.ArtifactClassLoader; import org.mule.runtime.module.artifact.classloader.ClassLoaderLookupPolicy; import org.mule.runtime.module.artifact.classloader.MuleArtifactClassLoader; import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptor; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.List; import java.util.Set; /** * Extends the default {@link ContainerClassLoaderFactory} for testing in order to add boot packages and build a * {@link ClassLoader} for the container that do resolves classes instead of delegating to its parent and also allows to create * the container {@link ClassLoaderLookupPolicy} based on a {@link ClassLoader}. * * @since 4.0 */ public class TestContainerClassLoaderFactory extends ContainerClassLoaderFactory implements AutoCloseable { private final Set<String> extraBootPackages; private final URL[] urls; private final URLClassLoader classLoader; private final DefaultModuleRepository testContainerModuleRepository; private ArtifactClassLoader containerClassLoader; /** * Factory class that extends the default way to create a container {@link ArtifactClassLoader} in order to support the * differences when running applications in standalone container vs junit. * * @param extraBootPackages {@link List} of {@link String}s extra boot packages that need to be appended to the container (junit * for instance) * @param urls {@link URL}s that were classified to be added to the container {@link ClassLoader} * @param moduleRepository provides access to the modules available on the container. Non null. */ public TestContainerClassLoaderFactory(final List<String> extraBootPackages, final URL[] urls, ModuleRepository moduleRepository) { super(moduleRepository); this.extraBootPackages = ImmutableSet.<String>builder().addAll(super.getBootPackages()).addAll(extraBootPackages) .addAll(new JreModuleDiscoverer().discover().get(0).getExportedPackages()).build(); this.urls = urls; this.classLoader = new URLClassLoader(urls, null); this.testContainerModuleRepository = new DefaultModuleRepository(new ContainerModuleDiscoverer(classLoader)); } /** * Overrides method due to it has to use the {@link ClassLoader} set to this factory in order to discover modules. * * @param parentClassLoader parent classLoader. Can be null. * @return a non null {@link ArtifactClassLoader} containing container code that can be used as parent classloader for other * mule artifacts. */ @Override public ArtifactClassLoader createContainerClassLoader(final ClassLoader parentClassLoader) { final List<MuleModule> muleModules = withContextClassLoader(classLoader, () -> testContainerModuleRepository.getModules()); MuleClassLoaderLookupPolicy lookupPolicy = new MuleClassLoaderLookupPolicy(Collections.emptyMap(), getBootPackages()); return createArtifactClassLoader(parentClassLoader, muleModules, lookupPolicy, new ArtifactDescriptor("mule")); } /** * Overrides the method in order to create a {@link ArtifactClassLoader} that will have a CHILD_FIRST * {@link ClassLoaderLookupPolicy}, it is needed due to as difference from a mule standalone container where the parent * {@link ClassLoader} for the container only has bootstrap jars plugins mule modules and third-party libraries when the runner * runs this tests using has a full class path with all the artifacts declared as dependencies for the artifact so we have to * change that and change the look strategy to be CHILD_FIRST for the container. * <p/> * The {@code muleModules} parameter will be ignored due to it has all the modules in classpath, instead they are discovered * once again but using a {@link URLClassLoader} that has the {@link URL}'s classified for the container {@link ClassLoader}. * * @param parentClassLoader the parent {@link ClassLoader} to delegate PARENT look ups * @param muleModules {@link MuleModule} discovered from the launcher {@link ClassLoader} but will be not considered here due to * it has all the class path * @param containerLookupPolicy the default {@link ClassLoaderLookupPolicy} defined for a container but will be ignored due to * it has to be different when running with a full class path as parent {@link ClassLoader} * @param artifactDescriptor * @return the {@link ArtifactClassLoader} to be used for the container */ @Override protected ArtifactClassLoader createArtifactClassLoader(final ClassLoader parentClassLoader, final List<MuleModule> muleModules, final ClassLoaderLookupPolicy containerLookupPolicy, ArtifactDescriptor artifactDescriptor) { final ArtifactDescriptor containerDescriptor = new ArtifactDescriptor("mule"); containerClassLoader = new MuleArtifactClassLoader(containerDescriptor.getName(), containerDescriptor, urls, parentClassLoader, containerLookupPolicy); return createContainerFilteringClassLoader(withContextClassLoader(classLoader, () -> testContainerModuleRepository.getModules()), containerClassLoader); } public ArtifactClassLoader getContainerClassLoader() { return containerClassLoader; } /** * @return the original list of boot packages defined in {@link ContainerClassLoaderFactory} plus extra packages needed to be * added in order to allow tests to run with isolation. For instance, junit is an extra package that has to be handled * as boot package. */ @Override public Set<String> getBootPackages() { return extraBootPackages; } public ClassLoaderLookupPolicy getContainerClassLoaderLookupPolicy(ClassLoader classLoader) { return super.getContainerClassLoaderLookupPolicy(classLoader, testContainerModuleRepository.getModules()); } /** * {@inheritDoc} */ @Override public void close() { try { classLoader.close(); } catch (IOException e) { throw new UncheckedIOException(e); } } }