All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mule.functional.junit4.ArtifactFunctionalTestCase Maven / Gradle / Ivy

The newest version!
/*
 * 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.functional.junit4;

import static java.util.Collections.singletonMap;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mule.runtime.core.api.config.MuleProperties.OBJECT_CLASSLOADER_REPOSITORY;
import static org.mule.test.runner.utils.AnnotationUtils.getAnnotationAttributeFrom;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.serialization.ObjectSerializer;
import org.mule.runtime.api.service.Service;
import org.mule.runtime.core.api.config.ConfigurationBuilder;
import org.mule.runtime.core.api.config.builders.SimpleConfigurationBuilder;
import org.mule.runtime.module.artifact.api.classloader.ArtifactClassLoader;
import org.mule.runtime.module.artifact.api.classloader.ClassLoaderRepository;
import org.mule.runtime.module.artifact.api.classloader.net.MuleArtifactUrlStreamHandler;
import org.mule.runtime.module.artifact.api.classloader.net.MuleUrlStreamHandlerFactory;
import org.mule.runtime.module.artifact.api.serializer.ArtifactObjectSerializer;
import org.mule.runtime.module.service.api.discoverer.ServiceDiscoverer;
import org.mule.runtime.module.service.api.manager.ServiceManager;
import org.mule.test.runner.ApplicationClassLoaderAware;
import org.mule.test.runner.ArtifactClassLoaderRunner;
import org.mule.test.runner.ContainerClassLoaderAware;
import org.mule.test.runner.PluginClassLoadersAware;
import org.mule.test.runner.RunnerDelegateTo;
import org.mule.test.runner.ServiceClassLoadersAware;
import org.mule.test.runner.api.ClassPathClassifier;
import org.mule.test.runner.api.IsolatedClassLoaderExtensionsManagerConfigurationBuilder;
import org.mule.test.runner.api.IsolatedServiceProviderDiscoverer;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.junit.After;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;

/**
 * Base class for running {@link FunctionalTestCase} with class loader isolation using {@link ArtifactClassLoaderRunner}, a JUnit
 * {@link org.junit.runner.Runner}. {@link ArtifactClassLoaderRunner} classifies the classpath provided by
 * IDE/surfire-maven-plugin generates an equivalent {@link ClassLoader} hierarchy as standalone mode.
 * 

* The classification is based on the maven dependencies declared by the pom maven artifact that the test belongs to. In order to * classify the {@link java.net.URL}s from the classpath it will use a dependency tree that holds also duplicates relationship for * the dependency graph. *

* The classification for {@link ClassLoader}s would be based on dependencies scope, provided will go to the container, compile to * plugin and test to the application. For more information about the classification process see {@link ClassPathClassifier}. *

* For plugins it will scan the plugin set of {@link java.net.URL}s to search for classes annotated with * {@link org.mule.runtime.extension.api.annotation.Extension}, if a class is annotated it will generate the metadata for the * extension in runtime and it will also register it to the {@link org.mule.runtime.core.api.extension.ExtensionManager}. Non * extension plugins will set its filter based on {@code mule-module.properties} file. *

* By default this test runs internally with a {@link org.junit.runners.BlockJUnit4ClassRunner} runner. On those cases where the * test has to be run with another runner the {@link RunnerDelegateTo} should be used to define it. *

* {@link PluginClassLoadersAware} will define that this class also needs to get access to plugin {@link ArtifactClassLoader} in * order to load extension classes (they are not exposed to the application) for registering them to the * {@link org.mule.runtime.core.api.extension.ExtensionManager}. *

* Due to the cost of reading the classpath, scanning the dependencies and classes to generate the {@link ClassLoader} is high, * this runner will hold an static reference to the {@link ClassLoader} created for the first test and will use the same during * the whole execution of the tests that are marked with the {@link RunWith} {@link ArtifactClassLoaderRunner}, so be aware that * static initializations or things related to this could be an issue and tests should be aware of this. * * @since 4.0 */ @RunWith(ArtifactClassLoaderRunner.class) public abstract class ArtifactFunctionalTestCase extends FunctionalTestCase { /** * As part of providing support for handling different artifacts without unzipping them, the factory for URL must be registered * and then the current protocol for mule artifacts {@link MuleArtifactUrlStreamHandler#PROTOCOL}. */ static { // register the custom UrlStreamHandlerFactory. MuleUrlStreamHandlerFactory.installUrlStreamHandlerFactory(); MuleArtifactUrlStreamHandler.register(); } public static final String SPRING_CONFIG_FILES_PROPERTIES = "spring.config.files"; public static final String MULE_SPRING_CONFIG_FILE = "mule-spring-config.xml"; private static List pluginClassLoaders; private static List serviceClassLoaders; private static ClassLoader containerClassLoader; private static ClassLoader applicationClassLoader; private static ServiceManager serviceRepository; private static ClassLoaderRepository classLoaderRepository; private static IsolatedClassLoaderExtensionsManagerConfigurationBuilder extensionsManagerConfigurationBuilder; private static TestServicesMuleContextConfigurator serviceConfigurator; @BeforeClass public static void configureClassLoaderRepository() { classLoaderRepository = new TestClassLoaderRepository(); } @Override @After public final void clearRequestContext() { // Nothing to do. } @Override protected ObjectSerializer getObjectSerializer() { return new ArtifactObjectSerializer(classLoaderRepository); } /** * @return thread context class loader has to be the application {@link ClassLoader} created by the runner. */ @Override protected ClassLoader getExecutionClassLoader() { return applicationClassLoader; } @PluginClassLoadersAware private static final void setPluginClassLoaders(List artifactClassLoaders) { if (artifactClassLoaders == null) { throw new IllegalArgumentException("A null value cannot be set as the plugins class loaders"); } if (pluginClassLoaders != null) { throw new IllegalStateException("Plugin class loaders were already set, it cannot be set again"); } pluginClassLoaders = artifactClassLoaders; if (!pluginClassLoaders.isEmpty()) { extensionsManagerConfigurationBuilder = new IsolatedClassLoaderExtensionsManagerConfigurationBuilder(pluginClassLoaders); extensionsManagerConfigurationBuilder.loadExtensionModels(); } } @ServiceClassLoadersAware private static final void setServiceClassLoaders(List artifactClassLoaders) { if (artifactClassLoaders == null) { throw new IllegalArgumentException("A null value cannot be set as the services class loaders"); } if (serviceClassLoaders != null) { throw new IllegalStateException("Service class loaders were already set, it cannot be set again"); } serviceClassLoaders = artifactClassLoaders; createServiceManager(); } @ContainerClassLoaderAware private static final void setContainerClassLoader(ClassLoader containerClassLoader) { if (containerClassLoader == null) { throw new IllegalArgumentException("A null value cannot be set as the container classLoader"); } if (ArtifactFunctionalTestCase.containerClassLoader != null) { throw new IllegalStateException("Container classloader was already set, it cannot be set again"); } ArtifactFunctionalTestCase.containerClassLoader = containerClassLoader; } @ApplicationClassLoaderAware private static final void setApplicationClassLoader(ClassLoader applicationClassLoader) { if (applicationClassLoader == null) { throw new IllegalArgumentException("A null value cannot be set as the application classLoader"); } if (ArtifactFunctionalTestCase.applicationClassLoader != null) { throw new IllegalStateException("Application classloader was already set, it cannot be set again"); } ArtifactFunctionalTestCase.applicationClassLoader = applicationClassLoader; } @Override protected ConfigurationBuilder getBuilder() throws Exception { ConfigurationBuilder builder = super.getBuilder(); assertThat(builder.getClass().getName(), is("org.mule.runtime.config.internal.SpringXmlConfigurationBuilder")); configureSpringXmlConfigurationBuilder(builder); return builder; } /** * Returns an instance of a given service if available * * @param serviceClass class of service to look for. Non null. * @param service class * @return an instance of the provided service type if it was declared as a dependency on the test, null otherwise. */ protected T getService(Class serviceClass) { Optional service = serviceRepository.getServices().stream().filter(s -> serviceClass.isAssignableFrom(s.getClass())).findFirst(); return service.isPresent() ? (T) service.get() : null; } protected void configureSpringXmlConfigurationBuilder(ConfigurationBuilder builder) { builder.addServiceConfigurator(serviceConfigurator); } private static void createServiceManager() { serviceRepository = ServiceManager.create(ServiceDiscoverer.create(new IsolatedServiceProviderDiscoverer(serviceClassLoaders))); try { serviceRepository.start(); } catch (MuleException e) { throw new IllegalStateException("Couldn't start service manager", e); } serviceConfigurator = new TestServicesMuleContextConfigurator(serviceRepository); } /** * Adds a {@link ConfigurationBuilder} that sets the {@link org.mule.runtime.core.api.extension.ExtensionManager} into the * {@link #muleContext}. This {@link ConfigurationBuilder} is set as the first element of the {@code builders} {@link List} * * @param builders the list of {@link ConfigurationBuilder}s that will be used to initialise the {@link #muleContext} */ @Override protected void addBuilders(List builders) { Class runner = getAnnotationAttributeFrom(this.getClass(), RunWith.class, "value"); if (runner == null || !runner.equals(ArtifactClassLoaderRunner.class)) { throw new IllegalStateException(this.getClass().getName() + " extends " + ArtifactFunctionalTestCase.class.getName() + " so it should be annotated to only run with: " + ArtifactClassLoaderRunner.class + ". See " + RunnerDelegateTo.class + " for defining a delegate runner to be used."); } if (extensionsManagerConfigurationBuilder != null) { builders.add(0, extensionsManagerConfigurationBuilder); } builders.add(0, new TestBootstrapServiceDiscovererConfigurationBuilder(containerClassLoader, getExecutionClassLoader(), pluginClassLoaders)); builders.add(0, new SimpleConfigurationBuilder(singletonMap(OBJECT_CLASSLOADER_REPOSITORY, classLoaderRepository))); } /** * Defines a {@link ClassLoaderRepository} with all the class loaders configured in the {@link ArtifactFunctionalTestCase} * class. */ protected static class TestClassLoaderRepository implements ClassLoaderRepository { private Map classLoaders = new HashMap<>(); public TestClassLoaderRepository() { registerClassLoader(Thread.currentThread().getContextClassLoader()); for (Object classLoader : serviceClassLoaders) { registerClassLoader(classLoader); } for (Object classLoader : pluginClassLoaders) { registerClassLoader(classLoader); } } private void registerClassLoader(Object classLoader) { if (isArtifactClassLoader(classLoader)) { try { Method getArtifactIdMethod = classLoader.getClass().getMethod("getArtifactId"); String artifactId = (String) getArtifactIdMethod.invoke(classLoader); classLoaders.put(artifactId, (ClassLoader) classLoader); } catch (Exception e) { throw new RuntimeException(e); } } } private boolean isArtifactClassLoader(Object classLoader) { Class clazz = classLoader.getClass(); while (clazz.getSuperclass() != null) { for (Class interfaceClass : clazz.getInterfaces()) { if (interfaceClass.getName().equals(ArtifactClassLoader.class.getName())) { return true; } } clazz = clazz.getSuperclass(); } return false; } @Override public Optional find(String classLoaderId) { return ofNullable(classLoaders.get(classLoaderId)); } @Override public Optional getId(ClassLoader classLoader) { for (String key : classLoaders.keySet()) { if (classLoaders.get(key) == classLoader) { return of(key); } } return empty(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy