/*
* 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.api;
import static java.util.Collections.emptySet;
import static org.mule.runtime.api.dsl.DslResolvingContext.getDefault;
import static org.mule.test.runner.api.MulePluginBasedLoaderFinder.META_INF_MULE_PLUGIN;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.core.DefaultMuleContext;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.extension.ExtensionManager;
import org.mule.runtime.core.config.builders.AbstractConfigurationBuilder;
import org.mule.runtime.module.artifact.classloader.ArtifactClassLoader;
import org.mule.runtime.module.extension.internal.manager.DefaultExtensionManagerFactory;
import org.mule.runtime.module.extension.internal.manager.ExtensionManagerFactory;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link org.mule.runtime.core.api.config.ConfigurationBuilder} that creates an
* {@link org.mule.runtime.core.api.extension.ExtensionManager}. It reads the extension manifest file using the extension class loader
* that loads the extension annotated class and register the extension to the manager.
*
* @since 4.0
*/
public class IsolatedClassLoaderExtensionsManagerConfigurationBuilder extends AbstractConfigurationBuilder {
private static Logger LOGGER = LoggerFactory.getLogger(IsolatedClassLoaderExtensionsManagerConfigurationBuilder.class);
private final ExtensionManagerFactory extensionManagerFactory;
private final List<ArtifactClassLoader> pluginsClassLoaders;
private final List<ExtensionModel> extensionModels = new ArrayList<>();
/**
* Creates an instance of the builder with the list of plugin class loaders. If an {@link ArtifactClassLoader} has a extension
* descriptor it will be registered as an extension if not it is assumed that it is not an extension plugin. The extension will
* be loaded and registered with its corresponding class loader in order to get access to the isolated {@link ClassLoader}
* defined for the extension.
*
* @param pluginsClassLoaders the list of {@link ArtifactClassLoader} created for each plugin found in the dependencies (either
* plugin or extension plugin).
*/
public IsolatedClassLoaderExtensionsManagerConfigurationBuilder(final List<ArtifactClassLoader> pluginsClassLoaders) {
this.extensionManagerFactory = new DefaultExtensionManagerFactory();
this.pluginsClassLoaders = pluginsClassLoaders;
}
/**
* Goes through the list of plugins {@link ArtifactClassLoader}s to check if they have an extension descriptor and if they do it
* will parse it and register the extension into the {@link org.mule.runtime.core.api.extension.ExtensionManager}
* <p/>
* It has to use reflection to access these classes due to the current execution of this method would be with the application
* {@link ArtifactClassLoader} and the list of plugin {@link ArtifactClassLoader} was instantiated with the Launcher
* {@link ClassLoader} so casting won't work here.
*
* @param muleContext The current {@link org.mule.runtime.core.api.MuleContext}
* @throws Exception if an error occurs while registering an extension of calling methods using reflection.
*/
@Override
protected void doConfigure(final MuleContext muleContext) throws Exception {
final ExtensionManager extensionManager = createExtensionManager(muleContext);
for (ExtensionModel extensionModel : extensionModels) {
extensionManager.registerExtension(extensionModel);
}
}
/**
* Creates an {@link ExtensionManager} to be used for registering the extensions.
*
* @param muleContext a {@link MuleContext} needed for creating the manager
* @return an {@link ExtensionManager}
* @throws InitialisationException if an error occurs while initializing the manager.
*/
private ExtensionManager createExtensionManager(final MuleContext muleContext) throws InitialisationException {
if (muleContext.getExtensionManager() != null) {
// TODO MULE-10982: implement a testing framework for XML based connectors, for now we workaround the current generation of the ExtensionManager if it was already created (see org.mule.test.operation.AbstractXmlExtensionMuleArtifactFunctionalTestCase)
return muleContext.getExtensionManager();
}
ExtensionManager extensionManager = extensionManagerFactory.create(muleContext);
((DefaultMuleContext) muleContext).setExtensionManager(extensionManager);
return extensionManager;
}
public void loadExtensionModels() {
try {
for (Object pluginClassLoader : pluginsClassLoaders) {
String artifactName = (String) pluginClassLoader.getClass().getMethod("getArtifactId").invoke(pluginClassLoader);
ClassLoader classLoader =
(ClassLoader) pluginClassLoader.getClass().getMethod("getClassLoader").invoke(pluginClassLoader);
Method findResource = classLoader.getClass().getMethod("findResource", String.class);
URL json = ((URL) findResource.invoke(classLoader, META_INF_MULE_PLUGIN));
if (json != null) {
LOGGER.debug("Discovered extension '{}'", artifactName);
MulePluginBasedLoaderFinder finder = new MulePluginBasedLoaderFinder(json.openStream());
ExtensionModel extension =
finder.getLoader().loadExtensionModel(classLoader, getDefault(emptySet()), finder.getParams());
extensionModels.add(extension);
} else {
LOGGER.debug("Discarding plugin with artifactName '{}' due to it doesn't have an mule-plugin.json", artifactName);
}
}
} catch (Exception e) {
throw new RuntimeException("Error while loading extension models", e);
}
}
}