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

org.sonarsource.sonarlint.core.plugin.PluginInstancesLoader Maven / Gradle / Ivy

/*
 * SonarLint Core - Implementation
 * Copyright (C) 2016-2021 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonarsource.sonarlint.core.plugin;

import com.google.common.base.Strings;
import java.io.Closeable;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.SystemUtils;
import org.sonar.api.Plugin;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import static org.apache.commons.lang.StringUtils.isNotEmpty;

/**
 * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
 * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current
 * environment (minimal sonarqube version, compatibility between plugins, ...):
 * 
    *
  • server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)
  • *
  • batch loads only the plugins deployed on server (see BatchPluginRepository)
  • *
*

* Plugins have their own isolated classloader, inheriting only from API classes. * Some plugins can extend a "base" plugin, sharing the same classloader. *

* This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.Plugin}. */ public class PluginInstancesLoader { private static final Logger LOG = Loggers.get(PluginInstancesLoader.class); private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"}; private static final String SLF4J_ADAPTER_JAR_NAME = "sonarlint-slf4j-sonar-log"; private final PluginJarExploder jarExploder; private final PluginClassloaderFactory classloaderFactory; private final TempFolder tempFolder; public PluginInstancesLoader(PluginJarExploder jarExploder, PluginClassloaderFactory classloaderFactory, TempFolder tempFolder) { this.jarExploder = jarExploder; this.classloaderFactory = classloaderFactory; this.tempFolder = tempFolder; } public Map load(Map infoByKeys) { File slf4jAdapter = extractSlf4jAdapterJar(); Collection defs = defineClassloaders(infoByKeys, slf4jAdapter); Map classloaders = classloaderFactory.create(defs); return instantiatePluginClasses(classloaders); } /** * Defines the different classloaders to be created. Number of classloaders can be * different than number of plugins. */ Collection defineClassloaders(Map infoByKeys, File slf4jAdapter) { Map classloadersByBasePlugin = new HashMap<>(); for (PluginInfo info : infoByKeys.values()) { String baseKey = basePluginKey(info, infoByKeys); if (baseKey == null) { continue; } PluginClassLoaderDef def = classloadersByBasePlugin.get(baseKey); if (def == null) { def = new PluginClassLoaderDef(baseKey); classloadersByBasePlugin.put(baseKey, def); } ExplodedPlugin explodedPlugin = jarExploder.explode(info); def.addFiles(Collections.singletonList(slf4jAdapter)); def.addFiles(Collections.singletonList(explodedPlugin.getMain())); def.addFiles(explodedPlugin.getLibs()); def.addMainClass(info.getKey(), info.getMainClass()); for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { def.getExportMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey())); } // The plugins that extend other plugins can only add some files to classloader. // They can't change metadata like ordering strategy or compatibility mode. if (Strings.isNullOrEmpty(info.getBasePlugin())) { def.setSelfFirstStrategy(info.isUseChildFirstClassLoader()); } } return classloadersByBasePlugin.values(); } private File extractSlf4jAdapterJar() { InputStream jarInputStream = PluginInstancesLoader.class.getResourceAsStream("/" + SLF4J_ADAPTER_JAR_NAME + ".jar"); try { File extractedJar = tempFolder.newFile(SLF4J_ADAPTER_JAR_NAME, ".jar"); FileUtils.copyInputStreamToFile(jarInputStream, extractedJar); return extractedJar; } catch (Exception e) { throw new IllegalStateException("Failed to extract the jar '" + SLF4J_ADAPTER_JAR_NAME + ".jar'"); } } /** * Instantiates collection of {@link org.sonar.api.Plugin} according to given metadata and classloaders * * @return the instances grouped by plugin key * @throws IllegalStateException if at least one plugin can't be correctly loaded */ Map instantiatePluginClasses(Map classloaders) { // instantiate plugins Map instancesByPluginKey = new HashMap<>(); for (Map.Entry entry : classloaders.entrySet()) { PluginClassLoaderDef def = entry.getKey(); ClassLoader classLoader = entry.getValue(); // the same classloader can be used by multiple plugins for (Map.Entry mainClassEntry : def.getMainClassesByPluginKey().entrySet()) { String pluginKey = mainClassEntry.getKey(); String mainClass = mainClassEntry.getValue(); try { instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).newInstance()); } catch (UnsupportedClassVersionError e) { throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s", pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e); } catch (Throwable e) { throw new IllegalStateException(String.format( "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e); } } } return instancesByPluginKey; } public void unload(Collection plugins) { for (Plugin plugin : plugins) { ClassLoader classLoader = plugin.getClass().getClassLoader(); if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassLoader()) { try { ((Closeable) classLoader).close(); } catch (Exception e) { Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e); } } } } /** * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then * B and C must be attached to the classloader of A. The method returns A in the three cases. */ @CheckForNull static String basePluginKey(PluginInfo plugin, Map allPluginsPerKey) { String base = plugin.getKey(); String parentKey = plugin.getBasePlugin(); while (isNotEmpty(parentKey)) { PluginInfo parentPlugin = allPluginsPerKey.get(parentKey); if (parentPlugin == null) { LOG.warn("Unable to find base plugin '{}' referenced by plugin '{}'", parentKey, base); return null; } base = parentPlugin.getKey(); parentKey = parentPlugin.getBasePlugin(); } return base; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy