Browse Source

SONAR-6749 add serverExtension support to Plugin class loaders

tags/5.2-RC1
Sébastien Lesaint 8 years ago
parent
commit
d2599d66ac

+ 10
- 0
sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderDef.java View File

@@ -46,6 +46,8 @@ class PluginClassloaderDef {
*/
private boolean compatibilityMode = false;

private boolean serverExtension = false;

PluginClassloaderDef(String basePluginKey) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(basePluginKey));
this.basePluginKey = basePluginKey;
@@ -93,6 +95,14 @@ class PluginClassloaderDef {
this.compatibilityMode = b;
}

boolean isServerExtension() {
return serverExtension;
}

void setServerExtension(boolean serverExtension) {
this.serverExtension = serverExtension;
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {

+ 3
- 2
sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java View File

@@ -65,9 +65,9 @@ public class PluginClassloaderFactory {
public Map<PluginClassloaderDef, ClassLoader> create(Collection<PluginClassloaderDef> defs) {
ClassloaderBuilder builder = new ClassloaderBuilder();
builder.newClassloader(API_CLASSLOADER_KEY, baseClassloader());
builder.setMask(API_CLASSLOADER_KEY, apiMask());

for (PluginClassloaderDef def : defs) {
builder.setMask(API_CLASSLOADER_KEY, def.isServerExtension() ? new Mask() : apiMask());
builder.newClassloader(def.getBasePluginKey());
builder.setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, new Mask());
builder.setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : PARENT_FIRST);
@@ -145,7 +145,7 @@ public class PluginClassloaderFactory {
*/
private static Mask apiMask() {
return new Mask()
.addInclusion("org/sonar/api/")
.addInclusion("org/sonar/api/")
.addInclusion("org/sonar/channel/")
.addInclusion("org/sonar/check/")
.addInclusion("org/sonar/colorizer/")
@@ -169,6 +169,7 @@ public class PluginClassloaderFactory {
.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/");

+ 18
- 5
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java View File

@@ -21,10 +21,12 @@ package org.sonar.core.platform;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.io.Closeable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.SystemUtils;
import org.sonar.api.Plugin;
import org.sonar.api.utils.log.Loggers;
@@ -44,11 +46,17 @@ import static java.util.Arrays.asList;
* Plugins have their own isolated classloader, inheriting only from API classes.
* Some plugins can extend a "base" plugin, sharing the same classloader.
* <p/>
* This class is stateless. It does not keep pointers to classloaders and {@link Plugin}.
* This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.SonarPlugin}.
*/
public class PluginLoader {

private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"};
/**
* Defines the base keys (defined by {@link #basePluginKey(PluginInfo, Map)}) of the plugins which are allowed to
* run a full server extensions.
*/
private static final Set<String> SYSTEM_EXTENSION_PLUGINS_BASE_KEYS = ImmutableSet.of("views");

public static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2");

private final PluginJarExploder jarExploder;
@@ -85,9 +93,9 @@ public class PluginLoader {
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()));
}
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.
@@ -96,6 +104,7 @@ public class PluginLoader {
Version minSqVersion = info.getMinimalSqVersion();
boolean compatibilityMode = minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0;
def.setCompatibilityMode(compatibilityMode);
def.setServerExtension(isServerExtension(baseKey));
if (compatibilityMode) {
Loggers.get(getClass()).debug("API compatibility mode is enabled on plugin {} [{}] " +
"(built with API lower than {})",
@@ -106,8 +115,12 @@ public class PluginLoader {
return classloadersByBasePlugin.values();
}

private static boolean isServerExtension(String basePluginKey) {
return SYSTEM_EXTENSION_PLUGINS_BASE_KEYS.contains(basePluginKey);
}

/**
* Instantiates collection of ({@link Plugin} according to given metadata and classloaders
* Instantiates collection of {@link org.sonar.api.SonarPlugin} 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

+ 41
- 0
sonar-core/src/test/java/org/sonar/classloader/MaskReader.java View File

@@ -0,0 +1,41 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.classloader;

import java.util.List;

/**
* Gives access to protected read methods of {@link Mask}.
*/
public class MaskReader {
private final Mask mask;

public MaskReader(Mask mask) {
this.mask = mask;
}

public List<String> getInclusions() {
return mask.getInclusions();
}

public List<String> getExclusions() {
return mask.getExclusions();
}
}

+ 29
- 0
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java View File

@@ -21,6 +21,7 @@ package org.sonar.core.platform;

import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -146,6 +147,34 @@ public class PluginLoaderTest {
// TODO test mask - require change in sonar-classloader
}

@Test
public void plugin_is_recognised_as_server_extension_if_key_is_views_and_extends_no_other_plugin_and_runs_in_compatibility_mode() throws IOException {
PluginInfo views = create52PluginInfo("views");

Collection<PluginClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("views", views));

assertThat(defs.iterator().next().isServerExtension()).isTrue();
}

@Test
public void plugin_is_not_recognised_as_system_extension_if_key_is_views_and_extends_another_plugin() throws IOException {
PluginInfo foo = create52PluginInfo("foo");
PluginInfo views = create52PluginInfo("views")
.setBasePlugin("foo");

Collection<PluginClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", foo, "views", views));

assertThat(defs).extracting("compatibilityMode").containsOnly(false, false);
}

private PluginInfo create52PluginInfo(String pluginKey) throws IOException {
File jarFile = temp.newFile();
return new PluginInfo(pluginKey)
.setJarFile(jarFile)
.setMainClass("org.foo." + pluginKey + "Plugin")
.setMinimalSqVersion(Version.create("5.2"));
}

/**
* Does not unzip jar file. It directly returns the JAR file defined on PluginInfo.
*/

Loading…
Cancel
Save