@@ -102,11 +102,6 @@ public class CePluginJarExploderTest { | |||
throw new UnsupportedOperationException(); | |||
} | |||
@Override | |||
public File getDeployDir() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
@Override | |||
public File getHomeDir() { | |||
throw new UnsupportedOperationException(); |
@@ -20,6 +20,7 @@ | |||
package org.sonar.db.plugin; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.builder.ToStringBuilder; | |||
public class PluginDto { | |||
@@ -59,7 +60,7 @@ public class PluginDto { | |||
return basePluginKey; | |||
} | |||
public PluginDto setBasePluginKey(String s) { | |||
public PluginDto setBasePluginKey(@Nullable String s) { | |||
this.basePluginKey = s; | |||
return this; | |||
} |
@@ -24,8 +24,6 @@ import java.io.File; | |||
/** | |||
* Replaces the incomplete {@link org.sonar.api.platform.ServerFileSystem} as many directories can't be | |||
* published in API. | |||
* | |||
* @since 6.0 | |||
*/ | |||
public interface ServerFileSystem { | |||
@@ -35,12 +33,6 @@ public interface ServerFileSystem { | |||
*/ | |||
File getDataDir(); | |||
/** | |||
* Directory accessible by scanners through web server | |||
* @return a directory which may or not exist | |||
*/ | |||
File getDeployDir(); | |||
/** | |||
* Root directory of the server installation | |||
* @return an existing directory | |||
@@ -89,7 +81,9 @@ public interface ServerFileSystem { | |||
/** | |||
* The file listing all the installed plugins. Used by scanner only. | |||
* @return an existing file | |||
* @deprecated see {@link org.sonar.server.startup.GeneratePluginIndex} | |||
*/ | |||
@Deprecated | |||
File getPluginIndex(); | |||
/** |
@@ -75,14 +75,9 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla | |||
return dataDir; | |||
} | |||
@Override | |||
public File getDeployDir() { | |||
return deployDir; | |||
} | |||
@Override | |||
public File getDeployedPluginsDir() { | |||
return new File(getDeployDir(), "plugins"); | |||
return new File(deployDir, "plugins"); | |||
} | |||
@Override | |||
@@ -107,7 +102,7 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla | |||
@Override | |||
public File getPluginIndex() { | |||
return new File(getDeployDir(), "plugins/index.txt"); | |||
return new File(deployDir, "plugins/index.txt"); | |||
} | |||
@Override |
@@ -36,7 +36,7 @@ import org.sonar.server.platform.db.migration.history.MigrationHistoryTable; | |||
import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl; | |||
import org.sonar.server.platform.db.migration.version.DatabaseVersion; | |||
import org.sonar.server.plugins.InstalledPluginReferentialFactory; | |||
import org.sonar.server.plugins.PluginCompression; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import org.sonar.server.plugins.ServerPluginJarExploder; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.plugins.WebServerExtensionInstaller; | |||
@@ -62,7 +62,7 @@ public class PlatformLevel2 extends PlatformLevel { | |||
ServerPluginRepository.class, | |||
ServerPluginJarExploder.class, | |||
PluginLoader.class, | |||
PluginCompression.class, | |||
PluginFileSystem.class, | |||
PluginClassloaderFactory.class, | |||
InstalledPluginReferentialFactory.class, | |||
WebServerExtensionInstaller.class, |
@@ -0,0 +1,81 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.server.plugins; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import javax.annotation.Nullable; | |||
import javax.annotation.concurrent.Immutable; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.core.platform.PluginInfo; | |||
import static java.util.Objects.requireNonNull; | |||
@Immutable | |||
public class InstalledPlugin { | |||
private final PluginInfo plugin; | |||
private final FileAndMd5 loadedJar; | |||
@Nullable | |||
private final FileAndMd5 compressedJar; | |||
public InstalledPlugin(PluginInfo plugin, FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) { | |||
this.plugin = requireNonNull(plugin); | |||
this.loadedJar = requireNonNull(loadedJar); | |||
this.compressedJar = compressedJar; | |||
} | |||
public PluginInfo getPluginInfo() { | |||
return plugin; | |||
} | |||
public FileAndMd5 getLoadedJar() { | |||
return loadedJar; | |||
} | |||
@Nullable | |||
public FileAndMd5 getCompressedJar() { | |||
return compressedJar; | |||
} | |||
@Immutable | |||
public static final class FileAndMd5 { | |||
private final File file; | |||
private final String md5; | |||
public FileAndMd5(File file) { | |||
try (InputStream fis = FileUtils.openInputStream(file)) { | |||
this.file = file; | |||
this.md5 = DigestUtils.md5Hex(fis); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Fail to compute md5 of " + file, e); | |||
} | |||
} | |||
public File getFile() { | |||
return file; | |||
} | |||
public String getMd5() { | |||
return md5; | |||
} | |||
} | |||
} |
@@ -1,101 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.server.plugins; | |||
import java.io.BufferedInputStream; | |||
import java.io.BufferedOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.jar.JarInputStream; | |||
import java.util.jar.Pack200; | |||
import java.util.zip.GZIPOutputStream; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.api.utils.log.Profiler; | |||
import org.sonar.core.platform.RemotePluginFile; | |||
import org.sonar.core.util.FileUtils; | |||
public class PluginCompression { | |||
private static final Logger LOG = Loggers.get(PluginCompression.class); | |||
static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable"; | |||
private final Map<String, RemotePluginFile> compressedPlugins = new HashMap<>(); | |||
private final Configuration configuration; | |||
public PluginCompression(Configuration configuration) { | |||
this.configuration = configuration; | |||
} | |||
public void compressJar(String pluginKey, Path sourceDir, Path targetJarFile) { | |||
if (configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) { | |||
Path targetPack200Path = FileUtils.getPack200FilePath(targetJarFile); | |||
Path sourcePack200Path = sourceDir.resolve(targetPack200Path.getFileName()); | |||
// check if packed file was deployed alongside the jar. If that's the case, use it instead of generating it (SONAR-10395). | |||
if (Files.isRegularFile(sourcePack200Path)) { | |||
try { | |||
LOG.debug("Found pack200: " + sourcePack200Path); | |||
Files.copy(sourcePack200Path, targetPack200Path); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200Path, e); | |||
} | |||
} else { | |||
pack200(targetJarFile, targetPack200Path, pluginKey); | |||
} | |||
String hash = calculateMd5(targetPack200Path); | |||
RemotePluginFile compressedPlugin = new RemotePluginFile(targetPack200Path.getFileName().toString(), hash); | |||
compressedPlugins.put(pluginKey, compressedPlugin); | |||
} | |||
} | |||
public Map<String, RemotePluginFile> getPlugins() { | |||
return new HashMap<>(compressedPlugins); | |||
} | |||
private static String calculateMd5(Path filePath) { | |||
try (InputStream fis = new BufferedInputStream(Files.newInputStream(filePath))) { | |||
return DigestUtils.md5Hex(fis); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Fail to compute hash", e); | |||
} | |||
} | |||
private static void pack200(Path jarPath, Path toPath, String pluginKey) { | |||
Profiler profiler = Profiler.create(LOG); | |||
profiler.startInfo("Compressing with pack200 plugin: " + pluginKey); | |||
Pack200.Packer packer = Pack200.newPacker(); | |||
try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jarPath))); | |||
OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(toPath)))) { | |||
packer.pack(in, out); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(String.format("Fail to pack200 plugin [%s] '%s' to '%s'", pluginKey, jarPath, toPath), e); | |||
} | |||
profiler.stopInfo(); | |||
} | |||
} |
@@ -0,0 +1,124 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.server.plugins; | |||
import java.io.BufferedInputStream; | |||
import java.io.BufferedOutputStream; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.Collection; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.jar.JarInputStream; | |||
import java.util.jar.Pack200; | |||
import java.util.zip.GZIPOutputStream; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.api.utils.log.Profiler; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; | |||
import static com.google.common.base.Preconditions.checkState; | |||
@ServerSide | |||
public class PluginFileSystem { | |||
public static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable"; | |||
private static final Logger LOG = Loggers.get(PluginFileSystem.class); | |||
private final Configuration configuration; | |||
private final Map<String, InstalledPlugin> installedFiles = new HashMap<>(); | |||
public PluginFileSystem(Configuration configuration) { | |||
this.configuration = configuration; | |||
} | |||
/** | |||
* @param plugin | |||
* @param loadedJar the JAR loaded by classloaders. It differs from {@code plugin.getJarFile()} | |||
* which is the initial location of JAR as seen by users | |||
*/ | |||
public void addInstalledPlugin(PluginInfo plugin, File loadedJar) { | |||
checkState(!installedFiles.containsKey(plugin.getKey()), "Plugin %s is already loaded", plugin.getKey()); | |||
checkState(loadedJar.exists(), "loadedJar does not exist: %s", loadedJar); | |||
Optional<File> compressed = compressJar(plugin, loadedJar); | |||
InstalledPlugin installedFile = new InstalledPlugin( | |||
plugin, | |||
new FileAndMd5(loadedJar), | |||
compressed.map(FileAndMd5::new).orElse(null)); | |||
installedFiles.put(plugin.getKey(), installedFile); | |||
} | |||
public Optional<InstalledPlugin> getInstalledPlugin(String pluginKey) { | |||
return Optional.ofNullable(installedFiles.get(pluginKey)); | |||
} | |||
public Collection<InstalledPlugin> getInstalledFiles() { | |||
return installedFiles.values(); | |||
} | |||
private Optional<File> compressJar(PluginInfo plugin, File jar) { | |||
if (!configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) { | |||
return Optional.empty(); | |||
} | |||
Path targetPack200 = getPack200Path(jar.toPath()); | |||
Path sourcePack200Path = getPack200Path(plugin.getNonNullJarFile().toPath()); | |||
// check if packed file was deployed alongside the jar. If that's the case, use it instead of generating it (SONAR-10395). | |||
if (sourcePack200Path.toFile().exists()) { | |||
try { | |||
LOG.debug("Found pack200: " + sourcePack200Path); | |||
Files.copy(sourcePack200Path, targetPack200); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200, e); | |||
} | |||
} else { | |||
pack200(jar.toPath(), targetPack200, plugin.getKey()); | |||
} | |||
return Optional.of(targetPack200.toFile()); | |||
} | |||
private static void pack200(Path jarPath, Path toPack200Path, String pluginKey) { | |||
Profiler profiler = Profiler.create(LOG); | |||
profiler.startInfo("Compressing plugin " + pluginKey + " [pack200]"); | |||
try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jarPath))); | |||
OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(toPack200Path)))) { | |||
Pack200.newPacker().pack(in, out); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(String.format("Fail to pack200 plugin [%s] '%s' to '%s'", pluginKey, jarPath, toPack200Path), e); | |||
} | |||
profiler.stopInfo(); | |||
} | |||
private static Path getPack200Path(Path jar) { | |||
String jarFileName = jar.getFileName().toString(); | |||
String filename = jarFileName.substring(0, jarFileName.length() - 3) + "pack.gz"; | |||
return jar.resolveSibling(filename); | |||
} | |||
} |
@@ -21,7 +21,6 @@ package org.sonar.server.plugins; | |||
import java.io.File; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.ce.ComputeEngineSide; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.ZipUtils; | |||
import org.sonar.core.platform.ExplodedPlugin; | |||
@@ -32,14 +31,13 @@ import org.sonar.server.platform.ServerFileSystem; | |||
import static org.apache.commons.io.FileUtils.forceMkdir; | |||
@ServerSide | |||
@ComputeEngineSide | |||
public class ServerPluginJarExploder extends PluginJarExploder { | |||
private final ServerFileSystem fs; | |||
private final PluginCompression pluginCompression; | |||
private final PluginFileSystem pluginFileSystem; | |||
public ServerPluginJarExploder(ServerFileSystem fs, PluginCompression pluginCompression) { | |||
public ServerPluginJarExploder(ServerFileSystem fs, PluginFileSystem pluginFileSystem) { | |||
this.fs = fs; | |||
this.pluginCompression = pluginCompression; | |||
this.pluginFileSystem = pluginFileSystem; | |||
} | |||
/** | |||
@@ -58,9 +56,10 @@ public class ServerPluginJarExploder extends PluginJarExploder { | |||
File jarTarget = new File(toDir, jarSource.getName()); | |||
FileUtils.copyFile(jarSource, jarTarget); | |||
pluginCompression.compressJar(pluginInfo.getKey(), jarSource.toPath().getParent(), jarTarget.toPath()); | |||
ZipUtils.unzip(jarSource, toDir, newLibFilter()); | |||
return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir); | |||
ExplodedPlugin explodedPlugin = explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir); | |||
pluginFileSystem.addInstalledPlugin(pluginInfo, jarTarget); | |||
return explodedPlugin; | |||
} catch (Exception e) { | |||
throw new IllegalStateException(String.format( | |||
"Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e); |
@@ -105,6 +105,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
@Override | |||
public void start() { | |||
long begin = System.currentTimeMillis(); | |||
loadPreInstalledPlugins(); | |||
copyBundledPlugins(); | |||
moveDownloadedPlugins(); |
@@ -21,9 +21,9 @@ package org.sonar.server.plugins.ws; | |||
import com.google.common.io.Resources; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.SortedSet; | |||
import java.util.function.Function; | |||
import org.sonar.api.server.ws.Change; | |||
@@ -31,20 +31,21 @@ import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.plugin.PluginDto; | |||
import org.sonar.server.plugins.PluginCompression; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import org.sonar.server.plugins.UpdateCenterMatrixFactory; | |||
import org.sonar.updatecenter.common.Plugin; | |||
import static com.google.common.collect.ImmutableSortedSet.copyOf; | |||
import static java.lang.String.format; | |||
import static java.util.Collections.emptyMap; | |||
import static java.util.Collections.singleton; | |||
import static java.util.stream.Collectors.toMap; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_COMPARATOR; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.categoryOrNull; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey; | |||
/** | |||
@@ -54,17 +55,13 @@ public class InstalledAction implements PluginsWsAction { | |||
private static final String ARRAY_PLUGINS = "plugins"; | |||
private static final String FIELD_CATEGORY = "category"; | |||
private final ServerPluginRepository pluginRepository; | |||
private final PluginWSCommons pluginWSCommons; | |||
private final PluginFileSystem pluginFileSystem; | |||
private final UpdateCenterMatrixFactory updateCenterMatrixFactory; | |||
private final DbClient dbClient; | |||
private final PluginCompression compression; | |||
public InstalledAction(ServerPluginRepository pluginRepository, PluginCompression compression, PluginWSCommons pluginWSCommons, | |||
public InstalledAction(PluginFileSystem pluginFileSystem, | |||
UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) { | |||
this.pluginRepository = pluginRepository; | |||
this.compression = compression; | |||
this.pluginWSCommons = pluginWSCommons; | |||
this.pluginFileSystem = pluginFileSystem; | |||
this.updateCenterMatrixFactory = updateCenterMatrixFactory; | |||
this.dbClient = dbClient; | |||
} | |||
@@ -93,31 +90,33 @@ public class InstalledAction implements PluginsWsAction { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
Collection<PluginInfo> pluginInfoList = searchPluginInfoList(); | |||
Map<String, PluginDto> pluginDtosByKey; | |||
Collection<InstalledPlugin> installedPlugins = loadInstalledPlugins(); | |||
Map<String, PluginDto> dtosByKey; | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
pluginDtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity())); | |||
dtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity())); | |||
} | |||
JsonWriter jsonWriter = response.newJsonWriter(); | |||
jsonWriter.setSerializeEmptys(false); | |||
jsonWriter.beginObject(); | |||
JsonWriter json = response.newJsonWriter(); | |||
json.setSerializeEmptys(false); | |||
json.beginObject(); | |||
List<String> additionalFields = request.paramAsStrings(WebService.Param.FIELDS); | |||
writePluginInfoList(jsonWriter, pluginInfoList, additionalFields == null ? Collections.emptyList() : additionalFields, pluginDtosByKey); | |||
Map<String, Plugin> updateCenterPlugins = (additionalFields == null || additionalFields.isEmpty()) ? emptyMap() : compatiblePluginsByKey(updateCenterMatrixFactory); | |||
jsonWriter.endObject(); | |||
jsonWriter.close(); | |||
} | |||
private SortedSet<PluginInfo> searchPluginInfoList() { | |||
return copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, pluginRepository.getPluginInfos()); | |||
json.name(ARRAY_PLUGINS); | |||
json.beginArray(); | |||
for (InstalledPlugin installedPlugin : copyOf(NAME_KEY_COMPARATOR, installedPlugins)) { | |||
PluginDto pluginDto = dtosByKey.get(installedPlugin.getPluginInfo().getKey()); | |||
Objects.requireNonNull(pluginDto, () -> format("Plugin %s is installed but not in DB", installedPlugin.getPluginInfo().getKey())); | |||
Plugin updateCenterPlugin = updateCenterPlugins.get(installedPlugin.getPluginInfo().getKey()); | |||
PluginWSCommons.writePluginInfo(json, installedPlugin.getPluginInfo(), categoryOrNull(updateCenterPlugin), pluginDto, installedPlugin); | |||
} | |||
json.endArray(); | |||
json.endObject(); | |||
json.close(); | |||
} | |||
private void writePluginInfoList(JsonWriter jsonWriter, Collection<PluginInfo> pluginInfoList, List<String> additionalFields, Map<String, PluginDto> pluginDtos) { | |||
Map<String, Plugin> compatiblesPluginsFromUpdateCenter = additionalFields.isEmpty() | |||
? Collections.emptyMap() | |||
: compatiblePluginsByKey(updateCenterMatrixFactory); | |||
pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsFromUpdateCenter, ARRAY_PLUGINS, pluginDtos, compression.getPlugins()); | |||
private SortedSet<InstalledPlugin> loadInstalledPlugins() { | |||
return copyOf(NAME_KEY_COMPARATOR, pluginFileSystem.getInstalledFiles()); | |||
} | |||
} |
@@ -22,6 +22,7 @@ package org.sonar.server.plugins.ws; | |||
import com.google.common.base.Function; | |||
import com.google.common.base.Predicate; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.ImmutableSortedSet; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
@@ -42,6 +43,8 @@ import org.sonar.updatecenter.common.Plugin; | |||
import static com.google.common.collect.FluentIterable.from; | |||
import static com.google.common.collect.ImmutableSet.copyOf; | |||
import static com.google.common.io.Resources.getResource; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.categoryOrNull; | |||
import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey; | |||
/** | |||
@@ -56,18 +59,15 @@ public class PendingAction implements PluginsWsAction { | |||
private final UserSession userSession; | |||
private final PluginDownloader pluginDownloader; | |||
private final ServerPluginRepository installer; | |||
private final PluginWSCommons pluginWSCommons; | |||
private final UpdateCenterMatrixFactory updateCenterMatrixFactory; | |||
private final PluginUninstaller pluginUninstaller; | |||
public PendingAction(UserSession userSession, PluginDownloader pluginDownloader, | |||
ServerPluginRepository installer, PluginUninstaller pluginUninstaller, | |||
PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { | |||
ServerPluginRepository installer, PluginUninstaller pluginUninstaller, UpdateCenterMatrixFactory updateCenterMatrixFactory) { | |||
this.userSession = userSession; | |||
this.pluginDownloader = pluginDownloader; | |||
this.installer = installer; | |||
this.pluginUninstaller = pluginUninstaller; | |||
this.pluginWSCommons = pluginWSCommons; | |||
this.updateCenterMatrixFactory = updateCenterMatrixFactory; | |||
} | |||
@@ -111,9 +111,19 @@ public class PendingAction implements PluginsWsAction { | |||
} | |||
} | |||
pluginWSCommons.writePluginInfoList(json, newPlugins, compatiblePluginsByKey, ARRAY_INSTALLING); | |||
pluginWSCommons.writePluginInfoList(json, updatedPlugins, compatiblePluginsByKey, ARRAY_UPDATING); | |||
pluginWSCommons.writePluginInfoList(json, uninstalledPlugins, compatiblePluginsByKey, ARRAY_REMOVING); | |||
writePlugin(json, ARRAY_INSTALLING, newPlugins, compatiblePluginsByKey); | |||
writePlugin(json, ARRAY_UPDATING, updatedPlugins, compatiblePluginsByKey); | |||
writePlugin(json, ARRAY_REMOVING, uninstalledPlugins, compatiblePluginsByKey); | |||
} | |||
private static void writePlugin(JsonWriter json, String propertyName, Collection<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey) { | |||
json.name(propertyName); | |||
json.beginArray(); | |||
for (PluginInfo pluginInfo : ImmutableSortedSet.copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { | |||
Plugin plugin = compatiblePluginsByKey.get(pluginInfo.getKey()); | |||
PluginWSCommons.writePluginInfo(json, pluginInfo, categoryOrNull(plugin), null, null); | |||
} | |||
json.endArray(); | |||
} | |||
private enum PluginInfoToKey implements Function<PluginInfo, String> { |
@@ -22,21 +22,19 @@ package org.sonar.server.plugins.ws; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import com.google.common.base.Function; | |||
import com.google.common.base.Optional; | |||
import com.google.common.base.Preconditions; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.Maps; | |||
import com.google.common.collect.Ordering; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nonnull; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.RemotePluginFile; | |||
import org.sonar.db.plugin.PluginDto; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.UpdateCenterMatrixFactory; | |||
import org.sonar.server.plugins.edition.EditionBundledPlugins; | |||
import org.sonar.updatecenter.common.Artifact; | |||
@@ -46,7 +44,6 @@ import org.sonar.updatecenter.common.Release; | |||
import org.sonar.updatecenter.common.UpdateCenter; | |||
import org.sonar.updatecenter.common.Version; | |||
import static com.google.common.collect.ImmutableSortedSet.copyOf; | |||
import static com.google.common.collect.Iterables.filter; | |||
import static com.google.common.collect.Iterables.transform; | |||
import static java.lang.String.CASE_INSENSITIVE_ORDER; | |||
@@ -57,9 +54,7 @@ public class PluginWSCommons { | |||
private static final String PROPERTY_KEY = "key"; | |||
private static final String PROPERTY_NAME = "name"; | |||
private static final String PROPERTY_HASH = "hash"; | |||
private static final String PROPERTY_COMPRESSED_HASH = "compressedHash"; | |||
private static final String PROPERTY_FILENAME = "filename"; | |||
private static final String PROPERTY_COMPRESSED_FILENAME = "compressedFilename"; | |||
private static final String PROPERTY_SONARLINT_SUPPORTED = "sonarLintSupported"; | |||
private static final String PROPERTY_DESCRIPTION = "description"; | |||
private static final String PROPERTY_LICENSE = "license"; | |||
@@ -86,6 +81,9 @@ public class PluginWSCommons { | |||
public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() | |||
.onResultOf(PluginInfo::getName) | |||
.compound(Ordering.natural().onResultOf(PluginInfo::getKey)); | |||
public static final Comparator<InstalledPlugin> NAME_KEY_COMPARATOR = Comparator | |||
.comparing((java.util.function.Function<InstalledPlugin, String>) installedPluginFile -> installedPluginFile.getPluginInfo().getName()) | |||
.thenComparing(f -> f.getPluginInfo().getKey()); | |||
public static final Comparator<Plugin> NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) | |||
.onResultOf(PluginToName.INSTANCE) | |||
.compound( | |||
@@ -93,22 +91,10 @@ public class PluginWSCommons { | |||
public static final Comparator<PluginUpdate> NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING) | |||
.onResultOf(PluginUpdateToPlugin.INSTANCE); | |||
void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable RemotePluginFile compressedPlugin) { | |||
public static void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable InstalledPlugin installedFile) { | |||
json.beginObject(); | |||
json.prop(PROPERTY_KEY, pluginInfo.getKey()); | |||
json.prop(PROPERTY_NAME, pluginInfo.getName()); | |||
if (pluginDto != null) { | |||
json.prop(PROPERTY_FILENAME, pluginInfo.getNonNullJarFile().getName()); | |||
json.prop(PROPERTY_SONARLINT_SUPPORTED, pluginInfo.isSonarLintSupported()); | |||
json.prop(PROPERTY_HASH, pluginDto.getFileHash()); | |||
json.prop(PROPERTY_UPDATED_AT, pluginDto.getUpdatedAt()); | |||
} | |||
if (compressedPlugin != null) { | |||
json.prop(PROPERTY_COMPRESSED_FILENAME, compressedPlugin.getFilename()); | |||
json.prop(PROPERTY_COMPRESSED_HASH, compressedPlugin.getHash()); | |||
} | |||
json.prop(PROPERTY_DESCRIPTION, pluginInfo.getDescription()); | |||
Version version = pluginInfo.getVersion(); | |||
if (version != null) { | |||
@@ -123,29 +109,15 @@ public class PluginWSCommons { | |||
json.prop(PROPERTY_HOMEPAGE_URL, pluginInfo.getHomepageUrl()); | |||
json.prop(PROPERTY_ISSUE_TRACKER_URL, pluginInfo.getIssueTrackerUrl()); | |||
json.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginInfo.getImplementationBuild()); | |||
json.endObject(); | |||
} | |||
public void writePluginInfoList(JsonWriter json, Iterable<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey, String propertyName) { | |||
writePluginInfoList(json, plugins, compatiblePluginsByKey, propertyName, null, null); | |||
} | |||
public void writePluginInfoList(JsonWriter json, Iterable<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey, String propertyName, | |||
@Nullable Map<String, PluginDto> pluginDtos, @Nullable Map<String, RemotePluginFile> compressedPlugins) { | |||
json.name(propertyName); | |||
json.beginArray(); | |||
for (PluginInfo pluginInfo : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { | |||
PluginDto pluginDto = null; | |||
if (pluginDtos != null) { | |||
pluginDto = pluginDtos.get(pluginInfo.getKey()); | |||
Preconditions.checkNotNull(pluginDto, "Plugin %s is installed but not in DB", pluginInfo.getKey()); | |||
} | |||
RemotePluginFile compressedPlugin = compressedPlugins != null ? compressedPlugins.get(pluginInfo.getKey()) : null; | |||
Plugin plugin = compatiblePluginsByKey.get(pluginInfo.getKey()); | |||
writePluginInfo(json, pluginInfo, categoryOrNull(plugin), pluginDto, compressedPlugin); | |||
if (pluginDto != null) { | |||
json.prop(PROPERTY_UPDATED_AT, pluginDto.getUpdatedAt()); | |||
} | |||
json.endArray(); | |||
if (installedFile != null) { | |||
json.prop(PROPERTY_FILENAME, installedFile.getLoadedJar().getFile().getName()); | |||
json.prop(PROPERTY_SONARLINT_SUPPORTED, installedFile.getPluginInfo().isSonarLintSupported()); | |||
json.prop(PROPERTY_HASH, installedFile.getLoadedJar().getMd5()); | |||
} | |||
json.endObject(); | |||
} | |||
public void writePlugin(JsonWriter jsonWriter, Plugin plugin) { |
@@ -32,28 +32,33 @@ import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.api.utils.log.Profiler; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.PluginRepository; | |||
import org.sonar.core.platform.RemotePlugin; | |||
import org.sonar.server.platform.ServerFileSystem; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
/** | |||
* The file deploy/plugins/index.txt is required for old versions of SonarLint. | |||
* They don't use the web service api/plugins/installed to get the list | |||
* of installed plugins. | |||
* https://jira.sonarsource.com/browse/SLCORE-146 | |||
*/ | |||
@ServerSide | |||
public final class GeneratePluginIndex implements Startable { | |||
private static final Logger LOG = Loggers.get(GeneratePluginIndex.class); | |||
private final ServerFileSystem fileSystem; | |||
private final PluginRepository repository; | |||
private final ServerFileSystem serverFs; | |||
private final PluginFileSystem pluginFs; | |||
public GeneratePluginIndex(ServerFileSystem fileSystem, PluginRepository repository) { | |||
this.fileSystem = fileSystem; | |||
this.repository = repository; | |||
public GeneratePluginIndex(ServerFileSystem serverFs, PluginFileSystem pluginFs) { | |||
this.serverFs = serverFs; | |||
this.pluginFs = pluginFs; | |||
} | |||
@Override | |||
public void start() { | |||
Profiler profiler = Profiler.create(LOG).startInfo("Generate scanner plugin index"); | |||
writeIndex(fileSystem.getPluginIndex()); | |||
writeIndex(serverFs.getPluginIndex()); | |||
profiler.stopDebug(); | |||
} | |||
@@ -62,12 +67,12 @@ public final class GeneratePluginIndex implements Startable { | |||
// Nothing to do | |||
} | |||
void writeIndex(File indexFile) { | |||
private void writeIndex(File indexFile) { | |||
try { | |||
FileUtils.forceMkdir(indexFile.getParentFile()); | |||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8)) { | |||
for (PluginInfo pluginInfo : repository.getPluginInfos()) { | |||
writer.append(RemotePlugin.create(pluginInfo).marshal()); | |||
for (InstalledPlugin plugin : pluginFs.getInstalledFiles()) { | |||
writer.append(toRow(plugin)); | |||
writer.append(CharUtils.LF); | |||
} | |||
writer.flush(); | |||
@@ -76,4 +81,17 @@ public final class GeneratePluginIndex implements Startable { | |||
throw new IllegalStateException("Unable to generate plugin index at " + indexFile, e); | |||
} | |||
} | |||
private static String toRow(InstalledPlugin file) { | |||
StringBuilder sb = new StringBuilder(); | |||
sb.append(file.getPluginInfo().getKey()) | |||
.append(",") | |||
.append(file.getPluginInfo().isSonarLintSupported()) | |||
.append(",") | |||
.append(file.getLoadedJar().getFile().getName()) | |||
.append("|") | |||
.append(file.getLoadedJar().getMd5()); | |||
return sb.toString(); | |||
} | |||
} |
@@ -19,38 +19,39 @@ | |||
*/ | |||
package org.sonar.server.startup; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import org.sonar.api.Startable; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.api.utils.log.Profiler; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.RemotePlugin; | |||
import org.sonar.core.util.UuidFactory; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.plugin.PluginDto; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import static java.util.function.Function.identity; | |||
/** | |||
* Take care to update the 'plugins' table at startup. | |||
*/ | |||
@ServerSide | |||
public class RegisterPlugins implements Startable { | |||
private static final Logger LOG = Loggers.get(RegisterPlugins.class); | |||
private final ServerPluginRepository repository; | |||
private final PluginFileSystem pluginFileSystem; | |||
private final DbClient dbClient; | |||
private final UuidFactory uuidFactory; | |||
private final System2 system; | |||
public RegisterPlugins(ServerPluginRepository repository, DbClient dbClient, UuidFactory uuidFactory, System2 system) { | |||
this.repository = repository; | |||
public RegisterPlugins(PluginFileSystem pluginFileSystem, DbClient dbClient, UuidFactory uuidFactory, System2 system) { | |||
this.pluginFileSystem = pluginFileSystem; | |||
this.dbClient = dbClient; | |||
this.uuidFactory = uuidFactory; | |||
this.system = system; | |||
@@ -59,7 +60,7 @@ public class RegisterPlugins implements Startable { | |||
@Override | |||
public void start() { | |||
Profiler profiler = Profiler.create(LOG).startInfo("Register plugins"); | |||
updateDB(repository.getPluginInfos()); | |||
updateDB(); | |||
profiler.stopDebug(); | |||
} | |||
@@ -68,30 +69,29 @@ public class RegisterPlugins implements Startable { | |||
// Nothing to do | |||
} | |||
private void updateDB(Collection<PluginInfo> pluginInfos) { | |||
private void updateDB() { | |||
long now = system.now(); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
Map<String, PluginDto> allPreviousPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream() | |||
.collect(Collectors.toMap(PluginDto::getKee, identity())); | |||
for (PluginInfo pluginInfo : pluginInfos) { | |||
RemotePlugin remotePlugin = RemotePlugin.create(pluginInfo); | |||
String newJarMd5 = remotePlugin.file().getHash(); | |||
PluginDto previousDto = allPreviousPluginsByKey.get(pluginInfo.getKey()); | |||
for (InstalledPlugin installed : pluginFileSystem.getInstalledFiles()) { | |||
PluginInfo info = installed.getPluginInfo(); | |||
PluginDto previousDto = allPreviousPluginsByKey.get(info.getKey()); | |||
if (previousDto == null) { | |||
LOG.debug("Register new plugin {}", pluginInfo.getKey()); | |||
LOG.debug("Register new plugin {}", info.getKey()); | |||
PluginDto pluginDto = new PluginDto() | |||
.setUuid(uuidFactory.create()) | |||
.setKee(pluginInfo.getKey()) | |||
.setBasePluginKey(pluginInfo.getBasePlugin()) | |||
.setFileHash(newJarMd5) | |||
.setKee(info.getKey()) | |||
.setBasePluginKey(info.getBasePlugin()) | |||
.setFileHash(installed.getLoadedJar().getMd5()) | |||
.setCreatedAt(now) | |||
.setUpdatedAt(now); | |||
dbClient.pluginDao().insert(dbSession, pluginDto); | |||
} else if (!previousDto.getFileHash().equals(newJarMd5)) { | |||
LOG.debug("Update plugin {}", pluginInfo.getKey()); | |||
} else if (!previousDto.getFileHash().equals(installed.getLoadedJar().getMd5())) { | |||
LOG.debug("Update plugin {}", info.getKey()); | |||
previousDto | |||
.setBasePluginKey(pluginInfo.getBasePlugin()) | |||
.setFileHash(newJarMd5) | |||
.setBasePluginKey(info.getBasePlugin()) | |||
.setFileHash(installed.getLoadedJar().getMd5()) | |||
.setUpdatedAt(now); | |||
dbClient.pluginDao().update(dbSession, previousDto); | |||
} |
@@ -1,89 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.server.plugins; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class PluginCompressionTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
private MapSettings settings = new MapSettings(); | |||
private Path targetJarPath; | |||
private Path targetFolder; | |||
private Path sourceFolder; | |||
private PluginCompression underTest; | |||
@Before | |||
public void setUp() throws IOException { | |||
sourceFolder = temp.newFolder("source").toPath(); | |||
targetFolder = temp.newFolder("target").toPath(); | |||
targetJarPath = targetFolder.resolve("test.jar"); | |||
Files.createFile(targetJarPath); | |||
} | |||
@Test | |||
public void disable_if_proparty_not_set() throws IOException { | |||
underTest = new PluginCompression(settings.asConfig()); | |||
underTest.compressJar("key", sourceFolder, targetJarPath); | |||
assertThat(Files.list(targetFolder)).containsOnly(targetJarPath); | |||
assertThat(underTest.getPlugins()).isEmpty(); | |||
} | |||
@Test | |||
public void should_compress_plugin() throws IOException { | |||
settings.setProperty(PluginCompression.PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); | |||
underTest = new PluginCompression(settings.asConfig()); | |||
underTest.compressJar("key", targetFolder, targetJarPath); | |||
assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz")); | |||
assertThat(underTest.getPlugins()).hasSize(1); | |||
assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz"); | |||
} | |||
@Test | |||
public void should_use_deployed_packed_file() throws IOException { | |||
Path packedPath = sourceFolder.resolve("test.pack.gz"); | |||
Files.write(packedPath, new byte[] {1, 2, 3}); | |||
settings.setProperty(PluginCompression.PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); | |||
underTest = new PluginCompression(settings.asConfig()); | |||
underTest.compressJar("key", sourceFolder, targetJarPath); | |||
assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz")); | |||
assertThat(underTest.getPlugins()).hasSize(1); | |||
assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz"); | |||
// check that the file was copied, not generated | |||
assertThat(targetFolder.resolve("test.pack.gz")).hasSameContentAs(packedPath); | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.server.plugins; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.lang.RandomStringUtils; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.core.platform.PluginInfo; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.server.plugins.PluginFileSystem.PROPERTY_PLUGIN_COMPRESSION_ENABLE; | |||
public class PluginFileSystemTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
private MapSettings settings = new MapSettings(); | |||
private Path targetJarPath; | |||
private Path targetFolder; | |||
private Path sourceFolder; | |||
@Before | |||
public void setUp() throws IOException { | |||
sourceFolder = temp.newFolder("source").toPath(); | |||
targetFolder = temp.newFolder("target").toPath(); | |||
targetJarPath = targetFolder.resolve("test.jar"); | |||
Files.createFile(targetJarPath); | |||
} | |||
@Test | |||
public void add_plugin_to_list_of_installed_plugins() throws IOException { | |||
File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); | |||
PluginInfo info = new PluginInfo("foo"); | |||
PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); | |||
underTest.addInstalledPlugin(info, jar); | |||
assertThat(underTest.getInstalledFiles()).hasSize(1); | |||
InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); | |||
assertThat(installedPlugin.getCompressedJar()).isNull(); | |||
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(jar.toPath()); | |||
assertThat(installedPlugin.getPluginInfo()).isSameAs(info); | |||
} | |||
@Test | |||
public void compress_jar_if_compression_enabled() throws IOException { | |||
File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); | |||
PluginInfo info = new PluginInfo("foo").setJarFile(jar); | |||
// the JAR is copied somewhere else in order to be loaded by classloaders | |||
File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); | |||
settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); | |||
PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); | |||
underTest.addInstalledPlugin(info, loadedJar); | |||
assertThat(underTest.getInstalledFiles()).hasSize(1); | |||
InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); | |||
assertThat(installedPlugin.getPluginInfo()).isSameAs(info); | |||
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath()); | |||
assertThat(installedPlugin.getCompressedJar().getFile()) | |||
.exists() | |||
.isFile() | |||
.hasName("sonar-foo-plugin.pack.gz") | |||
.hasParent(loadedJar.getParentFile()); | |||
} | |||
@Test | |||
public void copy_and_use_existing_packed_jar_if_compression_enabled() throws IOException { | |||
File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); | |||
File packedJar = touch(jar.getParentFile(), "sonar-foo-plugin.pack.gz"); | |||
PluginInfo info = new PluginInfo("foo").setJarFile(jar); | |||
// the JAR is copied somewhere else in order to be loaded by classloaders | |||
File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); | |||
settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); | |||
PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); | |||
underTest.addInstalledPlugin(info, loadedJar); | |||
assertThat(underTest.getInstalledFiles()).hasSize(1); | |||
InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); | |||
assertThat(installedPlugin.getPluginInfo()).isSameAs(info); | |||
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath()); | |||
assertThat(installedPlugin.getCompressedJar().getFile()) | |||
.exists() | |||
.isFile() | |||
.hasName(packedJar.getName()) | |||
.hasParent(loadedJar.getParentFile()) | |||
.hasSameContentAs(packedJar); | |||
} | |||
private static File touch(File dir, String filename) throws IOException { | |||
File file = new File(dir, filename); | |||
FileUtils.write(file, RandomStringUtils.random(10)); | |||
return file; | |||
} | |||
// | |||
// @Test | |||
// public void should_use_deployed_packed_file() throws IOException { | |||
// Path packedPath = sourceFolder.resolve("test.pack.gz"); | |||
// Files.write(packedPath, new byte[] {1, 2, 3}); | |||
// | |||
// settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); | |||
// underTest = new PluginFileSystem(settings.asConfig()); | |||
// underTest.compressJar("key", sourceFolder, targetJarPath); | |||
// | |||
// assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz")); | |||
// assertThat(underTest.getPlugins()).hasSize(1); | |||
// assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz"); | |||
// | |||
// // check that the file was copied, not generated | |||
// assertThat(targetFolder.resolve("test.pack.gz")).hasSameContentAs(packedPath); | |||
// } | |||
} |
@@ -37,16 +37,16 @@ public class ServerPluginJarExploderTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
ServerFileSystem fs = mock(ServerFileSystem.class); | |||
PluginCompression pluginCompression = mock(PluginCompression.class); | |||
ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginCompression); | |||
private ServerFileSystem fs = mock(ServerFileSystem.class); | |||
private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); | |||
private ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginFileSystem); | |||
@Test | |||
public void copy_all_classloader_files_to_dedicated_directory() throws Exception { | |||
File deployDir = temp.newFolder(); | |||
when(fs.getDeployedPluginsDir()).thenReturn(deployDir); | |||
File jar = TestProjectUtils.jarOf("test-libs-plugin"); | |||
PluginInfo info = PluginInfo.create(jar); | |||
File sourceJar = TestProjectUtils.jarOf("test-libs-plugin"); | |||
PluginInfo info = PluginInfo.create(sourceJar); | |||
ExplodedPlugin exploded = underTest.explode(info); | |||
@@ -61,6 +61,7 @@ public class ServerPluginJarExploderTest { | |||
assertThat(lib).exists().isFile(); | |||
assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath()); | |||
} | |||
verify(pluginCompression).compressJar(info.getKey(), jar.toPath().getParent(), exploded.getMain().toPath()); | |||
File targetJar = new File(fs.getDeployedPluginsDir(), "testlibs/test-libs-plugin-0.1-SNAPSHOT.jar"); | |||
verify(pluginFileSystem).addInstalledPlugin(info, targetJar); | |||
} | |||
} |
@@ -20,32 +20,34 @@ | |||
package org.sonar.server.plugins.ws; | |||
import com.google.common.base.Optional; | |||
import com.hazelcast.com.eclipsesource.json.Json; | |||
import com.hazelcast.com.eclipsesource.json.JsonObject; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.io.File; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.io.IOException; | |||
import java.util.Random; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.junit.runner.RunWith; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.WebService.Action; | |||
import org.sonar.api.server.ws.WebService.Param; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.RemotePluginFile; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.server.plugins.PluginCompression; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import org.sonar.server.plugins.UpdateCenterMatrixFactory; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonar.updatecenter.common.Plugin; | |||
import org.sonar.updatecenter.common.UpdateCenter; | |||
import org.sonar.updatecenter.common.Version; | |||
import static com.google.common.collect.ImmutableList.of; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.singletonList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; | |||
@@ -62,14 +64,14 @@ public class InstalledActionTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); | |||
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS); | |||
private PluginCompression pluginCompression = mock(PluginCompression.class); | |||
private InstalledAction underTest = new InstalledAction(pluginRepository, pluginCompression, new PluginWSCommons(), updateCenterMatrixFactory, db.getDbClient()); | |||
private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); | |||
private InstalledAction underTest = new InstalledAction(pluginFileSystem, updateCenterMatrixFactory, db.getDbClient()); | |||
private WsActionTester tester = new WsActionTester(underTest); | |||
@Test | |||
@@ -98,39 +100,62 @@ public class InstalledActionTest { | |||
} | |||
@Test | |||
public void empty_fields_are_not_serialized_to_json() { | |||
when(pluginRepository.getPluginInfos()).thenReturn( | |||
of(new PluginInfo("").setName("").setJarFile(new File("")))); | |||
public void empty_fields_are_not_serialized_to_json() throws IOException { | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn( | |||
singletonList(newInstalledPlugin(new PluginInfo("foo") | |||
.setName("") | |||
.setDescription("") | |||
.setLicense("") | |||
.setOrganizationName("") | |||
.setOrganizationUrl("") | |||
.setImplementationBuild("") | |||
.setHomepageUrl("") | |||
.setIssueTrackerUrl("")))); | |||
db.pluginDbTester().insertPlugin( | |||
p -> p.setKee(""), | |||
p -> p.setFileHash("abcdA"), | |||
p -> p.setUpdatedAt(111111L)); | |||
p -> p.setKee("foo"), | |||
p -> p.setUpdatedAt(100L)); | |||
String response = tester.newRequest().execute().getInput(); | |||
JsonObject json = Json.parse(response).asObject().get("plugins").asArray().get(0).asObject(); | |||
assertThat(json.get("key")).isNotNull(); | |||
assertThat(json.get("name")).isNull(); | |||
assertThat(json.get("description")).isNull(); | |||
assertThat(json.get("license")).isNull(); | |||
assertThat(json.get("organizationName")).isNull(); | |||
assertThat(json.get("organizationUrl")).isNull(); | |||
assertThat(json.get("homepageUrl")).isNull(); | |||
assertThat(json.get("issueTrackerUrl")).isNull(); | |||
assertThat(response).doesNotContain("name").doesNotContain("key"); | |||
} | |||
private InstalledPlugin newInstalledPlugin(PluginInfo plugin) throws IOException { | |||
FileAndMd5 jar = new FileAndMd5(temp.newFile()); | |||
return new InstalledPlugin(plugin.setJarFile(jar.getFile()), jar, null); | |||
} | |||
private InstalledPlugin newInstalledPluginWithCompression(PluginInfo plugin) throws IOException { | |||
FileAndMd5 jar = new FileAndMd5(temp.newFile()); | |||
FileAndMd5 compressedJar = new FileAndMd5(temp.newFile()); | |||
return new InstalledPlugin(plugin.setJarFile(jar.getFile()), jar, compressedJar); | |||
} | |||
@Test | |||
public void verify_properties_displayed_in_json_per_plugin() throws Exception { | |||
String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; | |||
when(pluginRepository.getPluginInfos()).thenReturn(of( | |||
new PluginInfo("plugKey") | |||
.setName("plugName") | |||
.setDescription("desc_it") | |||
.setVersion(Version.create("1.0")) | |||
.setLicense("license_hey") | |||
.setOrganizationName("org_name") | |||
.setOrganizationUrl("org_url") | |||
.setHomepageUrl("homepage_url") | |||
.setIssueTrackerUrl("issueTracker_url") | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setSonarLintSupported(true) | |||
.setJarFile(new File(getClass().getResource(jarFilename).toURI())))); | |||
public void return_default_fields() throws Exception { | |||
InstalledPlugin plugin = newInstalledPlugin(new PluginInfo("foo") | |||
.setName("plugName") | |||
.setDescription("desc_it") | |||
.setVersion(Version.create("1.0")) | |||
.setLicense("license_hey") | |||
.setOrganizationName("org_name") | |||
.setOrganizationUrl("org_url") | |||
.setHomepageUrl("homepage_url") | |||
.setIssueTrackerUrl("issueTracker_url") | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setSonarLintSupported(true)); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin)); | |||
db.pluginDbTester().insertPlugin( | |||
p -> p.setKee("plugKey"), | |||
p -> p.setFileHash("abcdplugKey"), | |||
p -> p.setUpdatedAt(111111L)); | |||
p -> p.setKee(plugin.getPluginInfo().getKey()), | |||
p -> p.setUpdatedAt(100L)); | |||
String response = tester.newRequest().execute().getInput(); | |||
@@ -140,7 +165,7 @@ public class InstalledActionTest { | |||
" \"plugins\":" + | |||
" [" + | |||
" {" + | |||
" \"key\": \"plugKey\"," + | |||
" \"key\": \"foo\"," + | |||
" \"name\": \"plugName\"," + | |||
" \"description\": \"desc_it\"," + | |||
" \"version\": \"1.0\"," + | |||
@@ -152,37 +177,32 @@ public class InstalledActionTest { | |||
" \"issueTrackerUrl\": \"issueTracker_url\"," + | |||
" \"implementationBuild\": \"sou_rev_sha1\"," + | |||
" \"sonarLintSupported\": true," + | |||
" \"filename\": \"some.jar\"," + | |||
" \"hash\": \"abcdplugKey\"," + | |||
" \"updatedAt\": 111111" + | |||
" \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," + | |||
" \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," + | |||
" \"updatedAt\": 100" + | |||
" }" + | |||
" ]" + | |||
"}"); | |||
} | |||
@Test | |||
public void add_compressed_plugin_info() throws Exception { | |||
RemotePluginFile compressedInfo = new RemotePluginFile("compressed.pack.gz", "hash"); | |||
when(pluginCompression.getPlugins()).thenReturn(Collections.singletonMap("plugKey", compressedInfo)); | |||
public void return_compression_fields_if_available() throws Exception { | |||
InstalledPlugin plugin = newInstalledPluginWithCompression(new PluginInfo("foo") | |||
.setName("plugName") | |||
.setDescription("desc_it") | |||
.setVersion(Version.create("1.0")) | |||
.setLicense("license_hey") | |||
.setOrganizationName("org_name") | |||
.setOrganizationUrl("org_url") | |||
.setHomepageUrl("homepage_url") | |||
.setIssueTrackerUrl("issueTracker_url") | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setSonarLintSupported(true)); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin)); | |||
String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; | |||
when(pluginRepository.getPluginInfos()).thenReturn(of( | |||
new PluginInfo("plugKey") | |||
.setName("plugName") | |||
.setDescription("desc_it") | |||
.setVersion(Version.create("1.0")) | |||
.setLicense("license_hey") | |||
.setOrganizationName("org_name") | |||
.setOrganizationUrl("org_url") | |||
.setHomepageUrl("homepage_url") | |||
.setIssueTrackerUrl("issueTracker_url") | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setSonarLintSupported(true) | |||
.setJarFile(new File(getClass().getResource(jarFilename).toURI())))); | |||
db.pluginDbTester().insertPlugin( | |||
p -> p.setKee("plugKey"), | |||
p -> p.setFileHash("abcdplugKey"), | |||
p -> p.setUpdatedAt(111111L)); | |||
p -> p.setKee(plugin.getPluginInfo().getKey()), | |||
p -> p.setUpdatedAt(100L)); | |||
String response = tester.newRequest().execute().getInput(); | |||
@@ -192,13 +212,11 @@ public class InstalledActionTest { | |||
" \"plugins\":" + | |||
" [" + | |||
" {" + | |||
" \"key\": \"plugKey\"," + | |||
" \"key\": \"foo\"," + | |||
" \"name\": \"plugName\"," + | |||
" \"description\": \"desc_it\"," + | |||
" \"version\": \"1.0\"," + | |||
" \"license\": \"license_hey\"," + | |||
" \"compressedFilename\": \"compressed.pack.gz\"," + | |||
" \"compressedHash\": \"hash\"," + | |||
" \"organizationName\": \"org_name\"," + | |||
" \"organizationUrl\": \"org_url\",\n" + | |||
" \"editionBundled\": false," + | |||
@@ -206,9 +224,9 @@ public class InstalledActionTest { | |||
" \"issueTrackerUrl\": \"issueTracker_url\"," + | |||
" \"implementationBuild\": \"sou_rev_sha1\"," + | |||
" \"sonarLintSupported\": true," + | |||
" \"filename\": \"some.jar\"," + | |||
" \"hash\": \"abcdplugKey\"," + | |||
" \"updatedAt\": 111111" + | |||
" \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," + | |||
" \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," + | |||
" \"updatedAt\": 100" + | |||
" }" + | |||
" ]" + | |||
"}"); | |||
@@ -217,8 +235,9 @@ public class InstalledActionTest { | |||
@Test | |||
public void category_is_returned_when_in_additional_fields() throws Exception { | |||
String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; | |||
when(pluginRepository.getPluginInfos()).thenReturn(of( | |||
new PluginInfo("plugKey") | |||
File jar = new File(getClass().getResource(jarFilename).toURI()); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(asList( | |||
new InstalledPlugin(new PluginInfo("plugKey") | |||
.setName("plugName") | |||
.setDescription("desc_it") | |||
.setVersion(Version.create("1.0")) | |||
@@ -228,11 +247,11 @@ public class InstalledActionTest { | |||
.setHomepageUrl("homepage_url") | |||
.setIssueTrackerUrl("issueTracker_url") | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setJarFile(new File(getClass().getResource(jarFilename).toURI())))); | |||
.setJarFile(jar), new FileAndMd5(jar), null))); | |||
UpdateCenter updateCenter = mock(UpdateCenter.class); | |||
when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter)); | |||
when(updateCenter.findAllCompatiblePlugins()).thenReturn( | |||
Arrays.asList( | |||
asList( | |||
Plugin.factory("plugKey") | |||
.setCategory("cat_1"))); | |||
@@ -242,7 +261,7 @@ public class InstalledActionTest { | |||
p -> p.setUpdatedAt(111111L)); | |||
String response = tester.newRequest() | |||
.setParam(Param.FIELDS, "category") | |||
.setParam(WebService.Param.FIELDS, "category") | |||
.execute().getInput(); | |||
assertJson(response).isSimilarTo( | |||
@@ -268,9 +287,9 @@ public class InstalledActionTest { | |||
} | |||
@Test | |||
public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() { | |||
when(pluginRepository.getPluginInfos()).thenReturn( | |||
of( | |||
public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws IOException { | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn( | |||
asList( | |||
plugin("A", "name2"), | |||
plugin("B", "name1"), | |||
plugin("C", "name0"), | |||
@@ -314,14 +333,15 @@ public class InstalledActionTest { | |||
Random random = new Random(); | |||
String organization = random.nextBoolean() ? "SonarSource" : "SONARSOURCE"; | |||
String pluginKey = "plugKey"; | |||
when(pluginRepository.getPluginInfos()).thenReturn(of( | |||
new PluginInfo(pluginKey) | |||
File jar = new File(getClass().getResource(jarFilename).toURI()); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(asList( | |||
new InstalledPlugin(new PluginInfo(pluginKey) | |||
.setName("plugName") | |||
.setVersion(Version.create("1.0")) | |||
.setLicense(license) | |||
.setOrganizationName(organization) | |||
.setImplementationBuild("sou_rev_sha1") | |||
.setJarFile(new File(getClass().getResource(jarFilename).toURI())))); | |||
.setJarFile(jar), new FileAndMd5(jar), null))); | |||
db.pluginDbTester().insertPlugin( | |||
p -> p.setKee(pluginKey), | |||
p -> p.setFileHash("abcdplugKey"), | |||
@@ -369,9 +389,9 @@ public class InstalledActionTest { | |||
} | |||
@Test | |||
public void only_one_plugin_can_have_a_specific_name_and_key() { | |||
when(pluginRepository.getPluginInfos()).thenReturn( | |||
of( | |||
public void only_one_plugin_can_have_a_specific_name_and_key() throws IOException { | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn( | |||
asList( | |||
plugin("A", "name2"), | |||
plugin("A", "name2"))); | |||
@@ -392,8 +412,13 @@ public class InstalledActionTest { | |||
assertThat(response).containsOnlyOnce("name2"); | |||
} | |||
private PluginInfo plugin(String key, String name) { | |||
return new PluginInfo(key).setName(name).setVersion(Version.create("1.0")).setJarFile(new File("sonar-" + key + "-plugin-1.0.jar")); | |||
private InstalledPlugin plugin(String key, String name) throws IOException { | |||
File file = temp.newFile(); | |||
PluginInfo info = new PluginInfo(key) | |||
.setName(name) | |||
.setVersion(Version.create("1.0")); | |||
info.setJarFile(file); | |||
return new InstalledPlugin(info, new FileAndMd5(file), null); | |||
} | |||
} |
@@ -60,7 +60,7 @@ public class PendingActionTest { | |||
private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class); | |||
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS); | |||
private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, | |||
pluginUninstaller, new PluginWSCommons(), updateCenterMatrixFactory); | |||
pluginUninstaller, updateCenterMatrixFactory); | |||
private Request request = mock(Request.class); | |||
private WsTester.TestResponse response = new WsTester.TestResponse(); | |||
@@ -20,11 +20,15 @@ | |||
package org.sonar.server.plugins.ws; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.RemotePluginFile; | |||
import org.sonar.db.plugin.PluginDto; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; | |||
import org.sonar.server.ws.WsTester; | |||
import org.sonar.updatecenter.common.Plugin; | |||
import org.sonar.updatecenter.common.PluginUpdate; | |||
@@ -42,13 +46,16 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UP | |||
public class PluginWSCommonsTest { | |||
WsTester.TestResponse response = new WsTester.TestResponse(); | |||
JsonWriter jsonWriter = response.newJsonWriter(); | |||
PluginWSCommons underTest = new PluginWSCommons(); | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
private WsTester.TestResponse response = new WsTester.TestResponse(); | |||
private JsonWriter jsonWriter = response.newJsonWriter(); | |||
private PluginWSCommons underTest = new PluginWSCommons(); | |||
@Test | |||
public void verify_properties_written_by_writePluginMetadata() { | |||
underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, null, null); | |||
PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, null, null); | |||
jsonWriter.close(); | |||
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" + | |||
@@ -66,8 +73,8 @@ public class PluginWSCommonsTest { | |||
@Test | |||
public void verify_properties_written_by_writePluginMetadata_with_dto() { | |||
PluginDto pluginDto = new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L); | |||
underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, null); | |||
PluginDto pluginDto = new PluginDto().setUpdatedAt(123456L); | |||
PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, null); | |||
jsonWriter.close(); | |||
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" + | |||
@@ -80,42 +87,40 @@ public class PluginWSCommonsTest { | |||
" \"organizationUrl\": \"http://www.sonarsource.com\"," + | |||
" \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," + | |||
" \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," + | |||
" \"filename\": \"sonar-scm-git-plugin-1.0.jar\"," + | |||
" \"hash\": \"abcdef123456\"," + | |||
" \"sonarLintSupported\": true," + | |||
" \"updatedAt\": 123456" + | |||
"}"); | |||
} | |||
@Test | |||
public void verify_properties_written_by_writeMetadata_with_compressed_plugin() { | |||
PluginDto pluginDto = new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L); | |||
RemotePluginFile compressedPlugin = new RemotePluginFile("compressed.pack.gz", "hash"); | |||
underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, compressedPlugin); | |||
public void verify_properties_written_by_writeMetadata_with_compressed_plugin() throws IOException { | |||
PluginDto dto = new PluginDto().setUpdatedAt(100L); | |||
FileAndMd5 loadedJar = new FileAndMd5(temp.newFile()); | |||
FileAndMd5 compressedJar = new FileAndMd5(temp.newFile()); | |||
InstalledPlugin installedFile = new InstalledPlugin(gitPluginInfo(), loadedJar, compressedJar); | |||
PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, dto, installedFile); | |||
jsonWriter.close(); | |||
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" + | |||
" \"key\": \"scmgit\"," + | |||
" \"name\": \"Git\"," + | |||
" \"description\": \"Git SCM Provider.\"," + | |||
" \"version\": \"1.0\"," + | |||
" \"license\": \"GNU LGPL 3\"," + | |||
" \"organizationName\": \"SonarSource\"," + | |||
" \"compressedFilename\": \"compressed.pack.gz\"," + | |||
" \"compressedHash\": \"hash\"," + | |||
" \"organizationUrl\": \"http://www.sonarsource.com\"," + | |||
" \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," + | |||
" \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," + | |||
" \"filename\": \"sonar-scm-git-plugin-1.0.jar\"," + | |||
" \"hash\": \"abcdef123456\"," + | |||
" \"sonarLintSupported\": true," + | |||
" \"updatedAt\": 123456" + | |||
" \"key\": \"scmgit\"," + | |||
" \"name\": \"Git\"," + | |||
" \"description\": \"Git SCM Provider.\"," + | |||
" \"version\": \"1.0\"," + | |||
" \"license\": \"GNU LGPL 3\"," + | |||
" \"organizationName\": \"SonarSource\"," + | |||
" \"organizationUrl\": \"http://www.sonarsource.com\"," + | |||
" \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," + | |||
" \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," + | |||
" \"sonarLintSupported\": true," + | |||
" \"updatedAt\": 100," + | |||
" \"filename\": \"" + loadedJar.getFile().getName() + "\"," + | |||
" \"hash\": \"" + loadedJar.getMd5() + "\"" + | |||
"}"); | |||
} | |||
@Test | |||
public void verify_properties_written_by_writeMetadata() { | |||
underTest.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null, null); | |||
PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null, null); | |||
jsonWriter.close(); | |||
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" + |
@@ -21,7 +21,6 @@ package org.sonar.server.startup; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Before; | |||
@@ -29,9 +28,12 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.PluginRepository; | |||
import org.sonar.server.platform.ServerFileSystem; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
@@ -41,30 +43,31 @@ public class GeneratePluginIndexTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
private ServerFileSystem fileSystem = mock(ServerFileSystem.class); | |||
private ServerFileSystem serverFileSystem = mock(ServerFileSystem.class); | |||
private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); | |||
private File index; | |||
@Before | |||
public void createIndexFile() throws IOException { | |||
index = temp.newFile(); | |||
when(fileSystem.getPluginIndex()).thenReturn(index); | |||
when(serverFileSystem.getPluginIndex()).thenReturn(index); | |||
} | |||
@Test | |||
public void shouldWriteIndex() throws IOException { | |||
PluginRepository repository = mock(PluginRepository.class); | |||
PluginInfo sqale = newInfo("sqale"); | |||
PluginInfo checkstyle = newInfo("checkstyle"); | |||
when(repository.getPluginInfos()).thenReturn(Arrays.asList(sqale, checkstyle)); | |||
InstalledPlugin javaPlugin = newInstalledPlugin("java", true); | |||
InstalledPlugin gitPlugin = newInstalledPlugin("scmgit", false); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(javaPlugin, gitPlugin)); | |||
GeneratePluginIndex underTest = new GeneratePluginIndex(fileSystem, repository); | |||
GeneratePluginIndex underTest = new GeneratePluginIndex(serverFileSystem, pluginFileSystem); | |||
underTest.start(); | |||
underTest.stop(); // For coverage | |||
List<String> lines = FileUtils.readLines(index); | |||
assertThat(lines).hasSize(2); | |||
assertThat(lines.get(0)).contains("sqale"); | |||
assertThat(lines.get(1)).contains("checkstyle"); | |||
assertThat(lines).containsExactly( | |||
"java,true," + javaPlugin.getLoadedJar().getFile().getName() + "|" + javaPlugin.getLoadedJar().getMd5(), | |||
"scmgit,false," + gitPlugin.getLoadedJar().getFile().getName() + "|" + gitPlugin.getLoadedJar().getMd5()); | |||
underTest.stop(); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
@@ -72,14 +75,14 @@ public class GeneratePluginIndexTest { | |||
File wrongParent = temp.newFile(); | |||
wrongParent.createNewFile(); | |||
File wrongIndex = new File(wrongParent, "index.txt"); | |||
when(fileSystem.getPluginIndex()).thenReturn(wrongIndex); | |||
PluginRepository repository = mock(PluginRepository.class); | |||
when(serverFileSystem.getPluginIndex()).thenReturn(wrongIndex); | |||
new GeneratePluginIndex(fileSystem, repository).start(); | |||
new GeneratePluginIndex(serverFileSystem, pluginFileSystem).start(); | |||
} | |||
private PluginInfo newInfo(String pluginKey) throws IOException { | |||
return new PluginInfo(pluginKey).setJarFile(temp.newFile(pluginKey + ".jar")); | |||
private InstalledPlugin newInstalledPlugin(String key, boolean supportSonarLint) throws IOException { | |||
FileAndMd5 jar = new FileAndMd5(temp.newFile()); | |||
PluginInfo pluginInfo = new PluginInfo(key).setJarFile(jar.getFile()).setSonarLintSupported(supportSonarLint); | |||
return new InstalledPlugin(pluginInfo, jar, null); | |||
} | |||
} |
@@ -22,6 +22,7 @@ package org.sonar.server.startup; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
@@ -32,7 +33,8 @@ import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.util.UuidFactory; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.plugins.InstalledPlugin; | |||
import org.sonar.server.plugins.PluginFileSystem; | |||
import static java.util.Arrays.asList; | |||
import static org.mockito.Mockito.mock; | |||
@@ -46,17 +48,13 @@ public class RegisterPluginsTest { | |||
@Rule | |||
public DbTester dbTester = DbTester.create(System2.INSTANCE); | |||
DbClient dbClient = dbTester.getDbClient(); | |||
private ServerPluginRepository serverPluginRepository; | |||
private UuidFactory uuidFactory; | |||
private System2 system2; | |||
private DbClient dbClient = dbTester.getDbClient(); | |||
private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); | |||
private UuidFactory uuidFactory = mock(UuidFactory.class); | |||
private System2 system2 = mock(System2.class); | |||
@Before | |||
public void prepare() { | |||
serverPluginRepository = mock(ServerPluginRepository.class); | |||
uuidFactory = mock(UuidFactory.class); | |||
system2 = mock(System2.class); | |||
public void setUp() { | |||
when(system2.now()).thenReturn(12345L).thenThrow(new IllegalStateException("Should be called only once")); | |||
} | |||
@@ -71,13 +69,16 @@ public class RegisterPluginsTest { | |||
FileUtils.write(fakeJavaJar, "fakejava", StandardCharsets.UTF_8); | |||
File fakeJavaCustomJar = temp.newFile(); | |||
FileUtils.write(fakeJavaCustomJar, "fakejavacustom", StandardCharsets.UTF_8); | |||
when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("java").setJarFile(fakeJavaJar), | |||
new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java"))); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(asList( | |||
newPlugin("java", fakeJavaJar, null), | |||
newPlugin("javacustom", fakeJavaCustomJar, "java"))); | |||
when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice")); | |||
RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2); | |||
RegisterPlugins register = new RegisterPlugins(pluginFileSystem, dbClient, uuidFactory, system2); | |||
register.start(); | |||
register.stop(); // For coverage | |||
dbTester.assertDbUnit(getClass(), "insert_new_plugins-result.xml", "plugins"); | |||
register.stop(); | |||
} | |||
/** | |||
@@ -89,11 +90,20 @@ public class RegisterPluginsTest { | |||
File fakeJavaCustomJar = temp.newFile(); | |||
FileUtils.write(fakeJavaCustomJar, "fakejavacustomchanged", StandardCharsets.UTF_8); | |||
when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java2"))); | |||
when(pluginFileSystem.getInstalledFiles()).thenReturn(asList( | |||
newPlugin("javacustom", fakeJavaCustomJar, "java2"))); | |||
new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2).start(); | |||
new RegisterPlugins(pluginFileSystem, dbClient, uuidFactory, system2).start(); | |||
dbTester.assertDbUnit(getClass(), "update_only_changed_plugins-result.xml", "plugins"); | |||
} | |||
private static InstalledPlugin newPlugin(String key, File file, @Nullable String basePlugin) { | |||
InstalledPlugin.FileAndMd5 jar = new InstalledPlugin.FileAndMd5(file); | |||
PluginInfo info = new PluginInfo(key) | |||
.setBasePlugin(basePlugin) | |||
.setJarFile(file); | |||
return new InstalledPlugin(info, jar, null); | |||
} | |||
} |
@@ -229,7 +229,6 @@ public class PluginInfo implements Comparable<PluginInfo> { | |||
return useChildFirstClassLoader; | |||
} | |||
@CheckForNull | |||
public boolean isSonarLintSupported() { | |||
return sonarLintSupported; | |||
} |