/* * 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.scanner.bootstrap; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.sonar.api.Plugin; 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.platform.RemotePluginFile; import org.sonar.home.cache.FileCache; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsResponse; import static java.lang.String.format; /** * Downloads the plugins installed on server and stores them in a local user cache * (see {@link FileCacheProvider}). */ public class ScannerPluginInstaller implements PluginInstaller { private static final Logger LOG = Loggers.get(ScannerPluginInstaller.class); private static final String PLUGINS_INDEX_URL = "/deploy/plugins/index.txt"; private final FileCache fileCache; private final ScannerPluginPredicate pluginPredicate; private final ScannerWsClient wsClient; public ScannerPluginInstaller(ScannerWsClient wsClient, FileCache fileCache, ScannerPluginPredicate pluginPredicate) { this.fileCache = fileCache; this.pluginPredicate = pluginPredicate; this.wsClient = wsClient; } @Override public Map<String, PluginInfo> installRemotes() { return loadPlugins(listRemotePlugins()); } private Map<String, PluginInfo> loadPlugins(List<RemotePlugin> remotePlugins) { Map<String, PluginInfo> infosByKey = new HashMap<>(remotePlugins.size()); Profiler profiler = Profiler.create(LOG).startDebug("Load plugins"); for (RemotePlugin remotePlugin : remotePlugins) { if (pluginPredicate.apply(remotePlugin.getKey())) { File jarFile = download(remotePlugin); PluginInfo info = PluginInfo.create(jarFile); infosByKey.put(info.getKey(), info); } } profiler.stopDebug(); return infosByKey; } /** * Returns empty on purpose. This method is used only by tests. * @see org.sonar.scanner.mediumtest.ScannerMediumTester */ @Override public Map<String, Plugin> installLocals() { return Collections.emptyMap(); } @VisibleForTesting File download(final RemotePlugin remote) { try { final RemotePluginFile file = remote.file(); return fileCache.get(file.getFilename(), file.getHash(), new FileDownloader(remote.getKey())); } catch (Exception e) { throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e); } } /** * Gets information about the plugins installed on server (filename, checksum) */ @VisibleForTesting List<RemotePlugin> listRemotePlugins() { try { String pluginIndex = loadPluginIndex(); String[] rows = StringUtils.split(pluginIndex, CharUtils.LF); List<RemotePlugin> result = new ArrayList<>(); for (String row : rows) { result.add(RemotePlugin.unmarshal(row)); } return result; } catch (Exception e) { throw new IllegalStateException("Fail to load plugin index: " + PLUGINS_INDEX_URL, e); } } private String loadPluginIndex() { Profiler profiler = Profiler.create(LOG).startInfo("Load plugins index"); GetRequest getRequest = new GetRequest(PLUGINS_INDEX_URL); String str; try (Reader reader = wsClient.call(getRequest).contentReader()) { str = IOUtils.toString(reader); } catch (IOException e) { throw new IllegalStateException(e); } profiler.stopInfo(); return str; } private class FileDownloader implements FileCache.Downloader { private String key; FileDownloader(String key) { this.key = key; } @Override public void download(String filename, File toFile) throws IOException { String url = format("/deploy/plugins/%s/%s", key, filename); if (LOG.isDebugEnabled()) { LOG.debug("Download plugin {} to {}", filename, toFile); } else { LOG.info("Download {}", filename); } WsResponse response = wsClient.call(new GetRequest(url)); try (InputStream stream = response.contentStream()) { FileUtils.copyInputStreamToFile(stream, toFile); } } } }