/* * SonarQube * Copyright (C) 2009-2017 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.sonar.core.platform; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.sonar.api.batch.ScannerSide; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.TempFolder; import org.sonar.classloader.ClassloaderBuilder; import org.sonar.classloader.Mask; import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.PARENT_FIRST; import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; /** * Builds the graph of classloaders to be used to instantiate plugins. It deals with: * <ul> * <li>isolation of plugins against core classes (except api)</li> * <li>backward-compatibility with plugins built for versions of SQ lower than 5.2. At that time * API declared transitive dependencies that were automatically available to plugins</li> * <li>sharing of some packages between plugins</li> * <li>loading of the libraries embedded in plugin JAR files (directory META-INF/libs)</li> * </ul> */ @ScannerSide @ServerSide @ComputeEngineSide public class PluginClassloaderFactory { // underscores are used to not conflict with plugin keys (if someday a plugin key is "api") private static final String API_CLASSLOADER_KEY = "_api_"; private final TempFolder temp; private URL compatibilityModeJar; public PluginClassloaderFactory(TempFolder temp) { this.temp = temp; } /** * Creates as many classloaders as requested by the input parameter. */ public Map<PluginClassLoaderDef, ClassLoader> create(Collection<PluginClassLoaderDef> defs) { ClassLoader baseClassLoader = baseClassLoader(); ClassloaderBuilder builder = new ClassloaderBuilder(); builder.newClassloader(API_CLASSLOADER_KEY, baseClassLoader); builder.setMask(API_CLASSLOADER_KEY, apiMask()); for (PluginClassLoaderDef def : defs) { builder.newClassloader(def.getBasePluginKey()); if (def.isPrivileged()) { builder.setParent(def.getBasePluginKey(), baseClassLoader, new Mask()); } else { builder.setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, new Mask()); } builder.setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : PARENT_FIRST); for (File jar : def.getFiles()) { builder.addURL(def.getBasePluginKey(), fileToUrl(jar)); } if (def.isCompatibilityMode()) { builder.addURL(def.getBasePluginKey(), extractCompatibilityModeJar()); } exportResources(def, builder, defs); } return build(defs, builder); } /** * A plugin can export some resources to other plugins */ private void exportResources(PluginClassLoaderDef def, ClassloaderBuilder builder, Collection<PluginClassLoaderDef> allPlugins) { // export the resources to all other plugins builder.setExportMask(def.getBasePluginKey(), def.getExportMask()); for (PluginClassLoaderDef other : allPlugins) { if (!other.getBasePluginKey().equals(def.getBasePluginKey())) { builder.addSibling(def.getBasePluginKey(), other.getBasePluginKey(), new Mask()); } } } /** * Builds classloaders and verifies that all of them are correctly defined */ private Map<PluginClassLoaderDef, ClassLoader> build(Collection<PluginClassLoaderDef> defs, ClassloaderBuilder builder) { Map<PluginClassLoaderDef, ClassLoader> result = new HashMap<>(); Map<String, ClassLoader> classloadersByBasePluginKey = builder.build(); for (PluginClassLoaderDef def : defs) { ClassLoader classloader = classloadersByBasePluginKey.get(def.getBasePluginKey()); if (classloader == null) { throw new IllegalStateException(String.format("Fail to create classloader for plugin [%s]", def.getBasePluginKey())); } result.put(def, classloader); } return result; } ClassLoader baseClassLoader() { return getClass().getClassLoader(); } private URL extractCompatibilityModeJar() { if (compatibilityModeJar == null) { File jar = temp.newFile("sonar-plugin-api-deps", "jar"); try { FileUtils.copyURLToFile(getClass().getResource("/sonar-plugin-api-deps.jar"), jar); compatibilityModeJar = jar.toURI().toURL(); } catch (Exception e) { throw new IllegalStateException("Can not extract sonar-plugin-api-deps.jar to " + jar.getAbsolutePath(), e); } } return compatibilityModeJar; } private static URL fileToUrl(File file) { try { return file.toURI().toURL(); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); } } /** * The resources (packages) that API exposes to plugins. Other core classes (SonarQube, MyBatis, ...) * can't be accessed. * <p>To sum-up, these are the classes packaged in sonar-plugin-api.jar or available as * a transitive dependency of sonar-plugin-api</p> */ private static Mask apiMask() { return new Mask() .addInclusion("org/sonar/api/") .addInclusion("org/sonar/channel/") .addInclusion("org/sonar/check/") .addInclusion("org/sonar/colorizer/") .addInclusion("org/sonar/duplications/") .addInclusion("org/sonar/graph/") .addInclusion("org/sonar/plugins/emailnotifications/api/") .addInclusion("net/sourceforge/pmd/") .addInclusion("org/apache/maven/") .addInclusion("org/codehaus/stax2/") .addInclusion("org/codehaus/staxmate/") .addInclusion("com/ctc/wstx/") .addInclusion("org/slf4j/") .addInclusion("javax/servlet/") // SLF4J bridges. Do not let plugins re-initialize and configure their logging system .addInclusion("org/apache/commons/logging/") .addInclusion("org/apache/log4j/") .addInclusion("ch/qos/logback/") // required for internal libs at SonarSource .addInclusion("org/sonar/server/platform/") .addInclusion("org/sonar/core/persistence/") .addInclusion("org/sonar/core/properties/") .addInclusion("org/sonar/server/views/") // API exclusions .addExclusion("org/sonar/api/internal/"); } }