From 8566436b507addd3b891cbe84a04e3e0225358ba Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Fri, 17 Jul 2015 10:05:49 +0200 Subject: [PATCH] SONAR-6577 Offline mode in preview mode --- .../batch/bootstrap/AnalysisProperties.java | 5 +- .../batch/bootstrap/BatchComponents.java | 2 +- .../batch/bootstrap/BatchPluginInstaller.java | 15 +- .../batch/bootstrap/BatchPluginPredicate.java | 8 +- .../batch/bootstrap/ExtensionInstaller.java | 10 +- .../batch/bootstrap/GlobalContainer.java | 12 +- .../org/sonar/batch/bootstrap/GlobalMode.java | 51 +++++ .../sonar/batch/bootstrap/GlobalSettings.java | 20 +- .../bootstrap/PersistentCacheProvider.java | 17 +- .../sonar/batch/bootstrap/ServerClient.java | 125 ++++------ .../org/sonar/batch/bootstrap/WSLoader.java | 182 +++++++++++++++ .../bootstrap/WSLoaderGlobalProvider.java | 46 ++++ .../sonar/batch/cpd/index/IndexFactory.java | 9 +- .../DefaultServerLineHashesLoader.java | 12 +- .../batch/mediumtest/BatchMediumTester.java | 4 +- .../org/sonar/batch/phases/PhaseExecutor.java | 5 +- .../sonar/batch/report/ReportPublisher.java | 13 +- .../DefaultGlobalRepositoriesLoader.java | 11 +- .../DefaultProjectRepositoriesLoader.java | 20 +- .../repository/DefaultServerIssuesLoader.java | 25 +- .../batch/repository/user/UserRepository.java | 36 ++- .../org/sonar/batch/scan/ModuleSettings.java | 7 +- .../ProjectAnalysisMode.java} | 27 ++- .../batch/scan/ProjectScanContainer.java | 7 +- .../org/sonar/batch/scan/ProjectSettings.java | 7 +- .../batch/scan/WSLoaderProjectProvider.java | 57 +++++ .../scan/filesystem/InputFileBuilder.java | 7 +- .../filesystem/InputFileBuilderFactory.java | 7 +- .../batch/scan/report/IssuesReports.java | 7 +- .../java/org/sonar/batch/util/BatchUtils.java | 12 + .../bootstrap/BatchPluginInstallerTest.java | 17 +- .../bootstrap/BatchPluginPredicateTest.java | 3 +- .../bootstrap/ExtensionInstallerTest.java | 4 +- .../sonar/batch/bootstrap/GlobalModeTest.java | 67 ++++++ .../batch/bootstrap/GlobalSettingsTest.java | 16 +- .../sonar/batch/bootstrap/MockHttpServer.java | 118 ++++++++++ .../PersistentCacheProviderTest.java | 15 -- .../batch/bootstrap/ServerClientTest.java | 208 +++-------------- .../bootstrap/WSLoaderGlobalProviderTest.java | 76 +++++++ .../sonar/batch/bootstrap/WSLoaderTest.java | 214 ++++++++++++++++++ .../bootstrap/WSLoaderTestWithServer.java | 112 +++++++++ .../batch/cpd/index/IndexFactoryTest.java | 8 +- .../DefaultServerLineHashesLoaderTest.java | 21 +- .../batch/report/ReportPublisherTest.java | 8 +- .../DefaultProjectRepositoriesLoaderTest.java | 26 +-- .../DefaultServerIssuesLoaderTest.java | 18 +- .../repository/user/UserRepositoryTest.java | 17 +- .../DefaultAnalysisModeTest.java | 39 ++-- .../sonar/batch/scan/ModuleSettingsTest.java | 5 +- .../sonar/batch/scan/ProjectSettingsTest.java | 16 +- .../scan/WSLoaderProjectProviderTest.java | 81 +++++++ .../InputFileBuilderFactoryTest.java | 6 +- .../scan/filesystem/InputFileBuilderTest.java | 5 +- .../core/util/DefaultHttpDownloader.java | 72 ++++-- .../core/util/DefaultHttpDownloaderTest.java | 21 +- .../org/sonar/home/cache/PersistentCache.java | 55 ++--- .../home/cache/PersistentCacheBuilder.java | 8 +- .../home/cache/PersistentCacheLoader.java | 26 +++ .../sonar/home/cache/PersistentCacheTest.java | 36 +-- .../java/org/sonar/api/CoreProperties.java | 5 + .../org/sonar/api/batch/AnalysisMode.java | 2 + .../sensor/internal/SensorContextTester.java | 10 + 62 files changed, 1512 insertions(+), 589 deletions(-) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoaderGlobalProvider.java rename sonar-batch/src/main/java/org/sonar/batch/{bootstrap/DefaultAnalysisMode.java => scan/ProjectAnalysisMode.java} (80%) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/scan/WSLoaderProjectProvider.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderGlobalProviderTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTestWithServer.java rename sonar-batch/src/test/java/org/sonar/batch/{bootstrap => scan}/DefaultAnalysisModeTest.java (60%) create mode 100644 sonar-batch/src/test/java/org/sonar/batch/scan/WSLoaderProjectProviderTest.java create mode 100644 sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/AnalysisProperties.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/AnalysisProperties.java index d8b53410f60..8a514c6db45 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/AnalysisProperties.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/AnalysisProperties.java @@ -28,7 +28,10 @@ import java.util.Map; * coming from sonar-project.properties). */ public class AnalysisProperties extends UserProperties { - + public AnalysisProperties(Map properties) { + this(properties, null); + } + public AnalysisProperties(Map properties, @Nullable String pathToSecretKey) { super(properties, pathToSecretKey); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java index 1704d303520..cb6f40dafc1 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java @@ -42,7 +42,7 @@ public class BatchComponents { // only static stuff } - public static Collection all(DefaultAnalysisMode analysisMode) { + public static Collection all(GlobalMode analysisMode) { List components = Lists.newArrayList( DefaultResourceTypes.get(), // SCM diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java index 3310365a856..b6744b5ff4a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -19,14 +19,18 @@ */ package org.sonar.batch.bootstrap; +import com.google.common.io.Files; + import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; + import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.sonar.api.Plugin; @@ -47,12 +51,12 @@ public class BatchPluginInstaller implements PluginInstaller { private static final Logger LOG = Loggers.get(BatchPluginInstaller.class); private static final String PLUGINS_INDEX_URL = "/deploy/plugins/index.txt"; - private final ServerClient server; + private final WSLoader wsLoader; private final FileCache fileCache; private final BatchPluginPredicate pluginPredicate; - public BatchPluginInstaller(ServerClient server, FileCache fileCache, BatchPluginPredicate pluginPredicate) { - this.server = server; + public BatchPluginInstaller(WSLoader wsLoader, FileCache fileCache, BatchPluginPredicate pluginPredicate) { + this.wsLoader = wsLoader; this.fileCache = fileCache; this.pluginPredicate = pluginPredicate; } @@ -95,7 +99,8 @@ public class BatchPluginInstaller implements PluginInstaller { } else { LOG.info("Download {}", file.getFilename()); } - server.download(url, toFile); + + Files.write(wsLoader.load(url), toFile); } }); @@ -111,7 +116,7 @@ public class BatchPluginInstaller implements PluginInstaller { List listRemotePlugins() { try { Profiler profiler = Profiler.create(LOG).startInfo("Load plugins index"); - String indexContent = server.request(PLUGINS_INDEX_URL); + String indexContent = wsLoader.loadString(PLUGINS_INDEX_URL); profiler.stopInfo(); String[] rows = StringUtils.split(indexContent, CharUtils.LF); List result = Lists.newArrayList(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java index 435b53d10db..dd55c277587 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java @@ -23,17 +23,19 @@ import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.Lists; + import java.text.MessageFormat; import java.util.List; import java.util.Set; + import javax.annotation.Nonnull; + import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; import org.sonar.api.batch.BatchSide; import org.sonar.api.config.Settings; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; - import static com.google.common.collect.Sets.newHashSet; /** @@ -50,9 +52,9 @@ public class BatchPluginPredicate implements Predicate { private final Set whites = newHashSet(); private final Set blacks = newHashSet(); - private final DefaultAnalysisMode mode; + private final GlobalMode mode; - public BatchPluginPredicate(Settings settings, DefaultAnalysisMode mode) { + public BatchPluginPredicate(Settings settings, GlobalMode mode) { this.mode = mode; if (mode.isPreview()) { // These default values are not supported by Settings because the class CorePlugin diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java index 2899ede562b..aa4f22e4e1e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java @@ -20,7 +20,9 @@ package org.sonar.batch.bootstrap; import java.util.List; + import javax.annotation.Nullable; + import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; import org.sonar.batch.bootstrapper.EnvironmentInformation; @@ -32,18 +34,18 @@ public class ExtensionInstaller { private final PluginRepository pluginRepository; private final EnvironmentInformation env; - private final DefaultAnalysisMode analysisMode; + private final GlobalMode globalMode; - public ExtensionInstaller(PluginRepository pluginRepository, EnvironmentInformation env, DefaultAnalysisMode analysisMode) { + public ExtensionInstaller(PluginRepository pluginRepository, EnvironmentInformation env, GlobalMode globalMode) { this.pluginRepository = pluginRepository; this.env = env; - this.analysisMode = analysisMode; + this.globalMode = globalMode; } public ExtensionInstaller install(ComponentContainer container, ExtensionMatcher matcher) { // core components - for (Object o : BatchComponents.all(analysisMode)) { + for (Object o : BatchComponents.all(globalMode)) { doInstall(container, matcher, null, o); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java index dd8660a3795..b61fae76904 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -21,6 +21,7 @@ package org.sonar.batch.bootstrap; import java.util.List; import java.util.Map; + import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.utils.Durations; @@ -52,7 +53,6 @@ import org.sonar.core.util.DefaultHttpDownloader; public class GlobalContainer extends ComponentContainer { private final Map bootstrapProperties; - private PersistentCacheProvider persistentCacheProvider; private GlobalContainer(Map bootstrapProperties) { super(); @@ -68,14 +68,11 @@ public class GlobalContainer extends ComponentContainer { @Override protected void doBeforeStart() { BootstrapProperties bootstrapProps = new BootstrapProperties(bootstrapProperties); - DefaultAnalysisMode analysisMode = new DefaultAnalysisMode(bootstrapProps.properties()); - add(bootstrapProps, analysisMode); + add(bootstrapProps); addBootstrapComponents(); } private void addBootstrapComponents() { - persistentCacheProvider = new PersistentCacheProvider(); - add( // plugins BatchPluginRepository.class, @@ -86,6 +83,7 @@ public class GlobalContainer extends ComponentContainer { ExtensionInstaller.class, CachesManager.class, + GlobalMode.class, GlobalSettings.class, ServerClient.class, Logback.class, @@ -94,7 +92,8 @@ public class GlobalContainer extends ComponentContainer { DefaultHttpDownloader.class, UriReader.class, new FileCacheProvider(), - persistentCacheProvider, + new PersistentCacheProvider(), + new WSLoaderGlobalProvider(), System2.INSTANCE, DefaultI18n.class, Durations.class, @@ -129,7 +128,6 @@ public class GlobalContainer extends ComponentContainer { public void executeAnalysis(Map analysisProperties, Object... components) { AnalysisProperties props = new AnalysisProperties(analysisProperties, this.getComponentByType(BootstrapProperties.class).property(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); - persistentCacheProvider.reconfigure(props); new ProjectScanContainer(this, props, components).execute(); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java new file mode 100644 index 00000000000..83c2249ad65 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java @@ -0,0 +1,51 @@ +/* + * 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.batch.bootstrap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.sonar.api.CoreProperties; + +import java.text.MessageFormat; + +public class GlobalMode { + private static final Logger LOG = LoggerFactory.getLogger(GlobalMode.class); + private boolean preview; + + public boolean isPreview() { + return preview; + } + + public GlobalMode(BootstrapProperties props) { + if (props.property(CoreProperties.DRY_RUN) != null) { + LOG.warn(MessageFormat.format("Property {0} is deprecated. Please use {1} instead.", CoreProperties.DRY_RUN, CoreProperties.ANALYSIS_MODE)); + preview = "true".equals(props.property(CoreProperties.DRY_RUN)); + } else { + String mode = props.property(CoreProperties.ANALYSIS_MODE); + preview = CoreProperties.ANALYSIS_MODE_PREVIEW.equals(mode) || CoreProperties.ANALYSIS_MODE_INCREMENTAL.equals(mode) || + CoreProperties.ANALYSIS_MODE_QUICK.equals(mode); + } + + if (preview) { + LOG.info("Preview global mode"); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java index 718dfd6acb4..db7f0c730fb 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java @@ -20,7 +20,9 @@ package org.sonar.batch.bootstrap; import com.google.common.collect.ImmutableMap; + import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; @@ -39,17 +41,17 @@ public class GlobalSettings extends Settings { * (what will happen, what should the user do, ...) as a value */ private static final Map DROPPED_PROPERTIES = ImmutableMap.of( - "sonar.jdbc.url", JDBC_SPECIFIC_MESSAGE, - "sonar.jdbc.username", JDBC_SPECIFIC_MESSAGE, - "sonar.jdbc.password", JDBC_SPECIFIC_MESSAGE - ); + "sonar.jdbc.url", JDBC_SPECIFIC_MESSAGE, + "sonar.jdbc.username", JDBC_SPECIFIC_MESSAGE, + "sonar.jdbc.password", JDBC_SPECIFIC_MESSAGE + ); private final BootstrapProperties bootstrapProps; private final GlobalRepositories globalReferentials; - private final DefaultAnalysisMode mode; + private final GlobalMode mode; public GlobalSettings(BootstrapProperties bootstrapProps, PropertyDefinitions propertyDefinitions, - GlobalRepositories globalReferentials, DefaultAnalysisMode mode) { + GlobalRepositories globalReferentials, GlobalMode mode) { super(propertyDefinitions); this.mode = mode; @@ -63,6 +65,12 @@ public class GlobalSettings extends Settings { private void init() { addProperties(globalReferentials.globalSettings()); addProperties(bootstrapProps.properties()); + + // To stay compatible with plugins that use the old property to check mode + if (mode.isPreview()) { + setProperty(CoreProperties.DRY_RUN, "true"); + } + LOG.info("Server id: " + getString(CoreProperties.SERVER_ID)); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PersistentCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PersistentCacheProvider.java index be8cce1a11e..323b5aa0a0e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PersistentCacheProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PersistentCacheProvider.java @@ -20,7 +20,6 @@ package org.sonar.batch.bootstrap; import java.nio.file.Paths; -import java.util.Map; import org.picocontainer.injectors.ProviderAdapter; import org.sonar.home.cache.PersistentCache; import org.sonar.home.cache.PersistentCacheBuilder; @@ -32,26 +31,14 @@ public class PersistentCacheProvider extends ProviderAdapter { if (cache == null) { PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger()); - builder.forceUpdate(isForceUpdate(props.properties())); - String home = props.property("sonar.userHome"); if (home != null) { builder.setSonarHome(Paths.get(home)); } cache = builder.build(); - } + } + return cache; } - - public void reconfigure(UserProperties props) { - if (cache != null) { - cache.reconfigure(isForceUpdate(props.properties())); - } - } - - private static boolean isForceUpdate(Map props) { - String enableCache = props.get("sonar.enableHttpCache"); - return !"true".equals(enableCache); - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java index 4f40634966f..66e2677691f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java @@ -19,26 +19,27 @@ */ package org.sonar.batch.bootstrap; +import org.sonar.api.utils.HttpDownloader.HttpException; + import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import com.google.common.io.Files; -import com.google.common.io.InputSupplier; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import java.io.ByteArrayInputStream; + import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; + import javax.annotation.Nullable; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; @@ -47,7 +48,6 @@ import org.sonar.api.batch.BatchSide; import org.sonar.api.utils.HttpDownloader; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.core.util.DefaultHttpDownloader; -import org.sonar.home.cache.PersistentCache; /** * Replace the deprecated org.sonar.batch.ServerMetadata @@ -60,29 +60,31 @@ public class ServerClient { private static final String GET = "GET"; private BootstrapProperties props; - private PersistentCache cache; private DefaultHttpDownloader.BaseHttpDownloader downloader; - private DefaultAnalysisMode mode; - public ServerClient(BootstrapProperties settings, EnvironmentInformation env, PersistentCache cache, DefaultAnalysisMode mode) { + public ServerClient(BootstrapProperties settings, EnvironmentInformation env) { this.props = settings; this.downloader = new DefaultHttpDownloader.BaseHttpDownloader(settings.properties(), env.toString()); - this.cache = cache; - this.mode = mode; } public String getURL() { return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/"); } + public URI getURI(String pathStartingWithSlash) { + Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /"); + String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash); + return URI.create(getURL() + path); + } + public void download(String pathStartingWithSlash, File toFile) { - download(pathStartingWithSlash, toFile, null); + download(pathStartingWithSlash, toFile, null, null); } - public void download(String pathStartingWithSlash, File toFile, @Nullable Integer readTimeoutMillis) { + public void download(String pathStartingWithSlash, File toFile, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) { try { - InputSupplier inputSupplier = doRequest(pathStartingWithSlash, GET, readTimeoutMillis); - Files.copy(inputSupplier, toFile); + InputStream is = load(pathStartingWithSlash, GET, false, connectTimeoutMillis, readTimeoutMillis); + Files.copy(is, toFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (HttpDownloader.HttpException he) { throw handleHttpException(he); } catch (IOException e) { @@ -90,80 +92,41 @@ public class ServerClient { } } - public String request(String pathStartingWithSlash) { - return request(pathStartingWithSlash, GET, true); - } - - public String request(String pathStartingWithSlash, String requestMethod) { - return request(pathStartingWithSlash, requestMethod, true); + public String downloadString(String pathStartingWithSlash) { + return downloadString(pathStartingWithSlash, GET, true, null); } - public String request(String pathStartingWithSlash, boolean wrapHttpException) { - return request(pathStartingWithSlash, GET, wrapHttpException, null); - } - - public String request(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException) { - return request(pathStartingWithSlash, requestMethod, wrapHttpException, null); - } - - public String request(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer timeoutMillis) { - final byte[] buf = load(pathStartingWithSlash, requestMethod, wrapHttpException, timeoutMillis); + public String downloadString(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer timeoutMillis) { + InputStream is = load(pathStartingWithSlash, requestMethod, wrapHttpException, null, timeoutMillis); try { - return new String(buf, "UTF-8"); - } catch (UnsupportedEncodingException e) { + return new String(IOUtils.toByteArray(is), "UTF-8"); + } catch (IOException e) { throw new IllegalStateException(String.format("Unable to request: %s", pathStartingWithSlash), e); } } - public InputSupplier doRequest(String pathStartingWithSlash, String requestMethod, @Nullable Integer timeoutMillis) { - final byte[] buf = load(pathStartingWithSlash, requestMethod, false, timeoutMillis); - - return new InputSupplier() { - @Override - public InputStream getInput() throws IOException { - return new ByteArrayInputStream(buf); - } - }; - } - - private byte[] load(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer timeoutMillis) { - Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /"); - String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash); - URI uri = URI.create(getURL() + path); + /** + * @throws IllegalStateException on I/O error, not limited to the network connection and if HTTP response code > 400 and wrapHttpException is true + * @throws HttpException if HTTP response code > 400 and wrapHttpException is false + */ + public InputStream load(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer connectTimeoutMs, + @Nullable Integer readTimeoutMs) { + URI uri = getURI(pathStartingWithSlash); try { - if (GET.equals(requestMethod) && mode.isPreview()) { - return cache.get(uri.toString(), new HttpValueLoader(uri, requestMethod, timeoutMillis)); + if (Strings.isNullOrEmpty(getLogin())) { + return downloader.newInputSupplier(uri, requestMethod, connectTimeoutMs, readTimeoutMs).getInput(); } else { - return new HttpValueLoader(uri, requestMethod, timeoutMillis).call(); + return downloader.newInputSupplier(uri, requestMethod, getLogin(), getPassword(), connectTimeoutMs, readTimeoutMs).getInput(); } } catch (HttpDownloader.HttpException e) { - throw wrapHttpException ? handleHttpException(e) : e; - } catch (Exception e) { - throw new IllegalStateException(String.format("Unable to request: %s", uri), e); - } - } - - private class HttpValueLoader implements Callable { - private URI uri; - private String requestMethod; - private Integer timeoutMillis; - - public HttpValueLoader(URI uri, String requestMethod, Integer timeoutMillis) { - this.uri = uri; - this.requestMethod = requestMethod; - this.timeoutMillis = timeoutMillis; - } - - @Override - public byte[] call() throws Exception { - InputSupplier inputSupplier; - if (Strings.isNullOrEmpty(getLogin())) { - inputSupplier = downloader.newInputSupplier(uri, requestMethod, timeoutMillis); + if (wrapHttpException) { + throw handleHttpException(e); } else { - inputSupplier = downloader.newInputSupplier(uri, requestMethod, getLogin(), getPassword(), timeoutMillis); + throw e; } - return IOUtils.toByteArray(inputSupplier.getInput()); + } catch (IOException e) { + throw new IllegalStateException(String.format("Unable to request: %s", uri), e); } } @@ -207,14 +170,4 @@ public class ServerClient { public String getPassword() { return props.property(CoreProperties.PASSWORD); } - - public static String encodeForUrl(String url) { - try { - return URLEncoder.encode(url, "UTF-8"); - - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Encoding not supported", e); - } - } - } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java new file mode 100644 index 00000000000..d475e68c360 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java @@ -0,0 +1,182 @@ +/* + * 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.batch.bootstrap; + +import javax.annotation.Nonnull; + +import org.sonar.api.utils.HttpDownloader; +import com.google.common.io.ByteSource; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.sonar.batch.bootstrap.WSLoader.ServerStatus.*; +import static org.sonar.batch.bootstrap.WSLoader.LoadStrategy.*; +import org.sonar.home.cache.PersistentCache; + +public class WSLoader { + private static final String FAIL_MSG = "Server is not accessible and data is not cached"; + private static final int CONNECT_TIMEOUT = 5000; + private static final int READ_TIMEOUT = 10000; + private static final String REQUEST_METHOD = "GET"; + + public enum ServerStatus { + UNKNOWN, ACCESSIBLE, NOT_ACCESSIBLE; + } + + public enum LoadStrategy { + SERVER_FIRST, CACHE_FIRST; + } + + private LoadStrategy loadStrategy; + private boolean cacheEnabled; + private ServerStatus serverStatus; + private ServerClient client; + private PersistentCache cache; + + public WSLoader(boolean cacheEnabled, PersistentCache cache, ServerClient client) { + this.cacheEnabled = cacheEnabled; + this.loadStrategy = CACHE_FIRST; + this.serverStatus = UNKNOWN; + this.cache = cache; + this.client = client; + } + + public WSLoader(PersistentCache cache, ServerClient client) { + this(false, cache, client); + } + + public ByteSource loadSource(String id) { + return ByteSource.wrap(load(id)); + } + + public String loadString(String id) { + return new String(load(id), StandardCharsets.UTF_8); + } + + @Nonnull + public byte[] load(String id) { + if (loadStrategy == CACHE_FIRST) { + return loadFromCacheFirst(id); + } else { + return loadFromServerFirst(id); + } + } + + public void setStrategy(LoadStrategy strategy) { + this.loadStrategy = strategy; + } + + public LoadStrategy getStrategy() { + return this.loadStrategy; + } + + public void setCacheEnabled(boolean enabled) { + this.cacheEnabled = enabled; + } + + public boolean isCacheEnabled() { + return this.cacheEnabled; + } + + private void switchToOffline() { + serverStatus = NOT_ACCESSIBLE; + } + + private void switchToOnline() { + serverStatus = ACCESSIBLE; + } + + private boolean isOffline() { + return serverStatus == NOT_ACCESSIBLE; + } + + @Nonnull + private byte[] loadFromCacheFirst(String id) { + byte[] cached = loadFromCache(id); + if (cached != null) { + return cached; + } + + try { + return loadFromServer(id); + } catch (Exception e) { + if (e.getCause() instanceof HttpDownloader.HttpException) { + throw e; + } + } + + throw new IllegalStateException(FAIL_MSG); + } + + @Nonnull + private byte[] loadFromServerFirst(String id) { + try { + return loadFromServer(id); + } catch (Exception serverException) { + if (serverException.getCause() instanceof HttpDownloader.HttpException) { + // http exceptions should always be thrown (no fallback) + throw serverException; + } + byte[] cached = loadFromCache(id); + if (cached != null) { + return cached; + } + } + + throw new IllegalStateException(FAIL_MSG); + } + + private byte[] loadFromCache(String id) { + if (!cacheEnabled) { + return null; + } + + try { + return cache.get(client.getURI(id).toString(), null); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private byte[] loadFromServer(String id) { + if (isOffline()) { + throw new IllegalStateException("Server is not accessible"); + } + + try { + InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, READ_TIMEOUT); + switchToOnline(); + byte[] value = IOUtils.toByteArray(is); + if (cacheEnabled) { + cache.put(client.getURI(id).toString(), value); + } + return value; + } catch (IllegalStateException e) { + switchToOffline(); + throw e; + } catch (Exception e) { + switchToOffline(); + throw new IllegalStateException(e); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoaderGlobalProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoaderGlobalProvider.java new file mode 100644 index 00000000000..2d9a76252a8 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoaderGlobalProvider.java @@ -0,0 +1,46 @@ +/* + * 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.batch.bootstrap; + +import org.picocontainer.injectors.ProviderAdapter; + +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; + +import java.util.Map; + +import org.sonar.home.cache.PersistentCache; + +public class WSLoaderGlobalProvider extends ProviderAdapter { + private static final LoadStrategy DEFAULT_STRATEGY = LoadStrategy.SERVER_FIRST; + private WSLoader wsLoader; + + public WSLoader provide(BootstrapProperties props, GlobalMode mode, PersistentCache cache, ServerClient client) { + if (wsLoader == null) { + wsLoader = new WSLoader(isCacheEnabled(props.properties(), mode.isPreview()), cache, client); + wsLoader.setStrategy(DEFAULT_STRATEGY); + } + return wsLoader; + } + + private static boolean isCacheEnabled(Map props, boolean isPreview) { + String enableOffline = props.get("sonar.enableOffline"); + return isPreview && "true".equals(enableOffline); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/cpd/index/IndexFactory.java b/sonar-batch/src/main/java/org/sonar/batch/cpd/index/IndexFactory.java index 26fa317b8bb..d9d6a444080 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cpd/index/IndexFactory.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cpd/index/IndexFactory.java @@ -19,23 +19,26 @@ */ package org.sonar.batch.cpd.index; +import org.sonar.batch.scan.ProjectAnalysisMode; + import com.google.common.annotations.VisibleForTesting; + import javax.annotation.Nullable; + import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.sonar.api.CoreProperties; import org.sonar.api.batch.BatchSide; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; @BatchSide public class IndexFactory { private final Settings settings; - private final DefaultAnalysisMode mode; + private final ProjectAnalysisMode mode; - public IndexFactory(DefaultAnalysisMode mode, Settings settings) { + public IndexFactory(ProjectAnalysisMode mode, Settings settings) { this.mode = mode; this.settings = settings; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java index f7fced2829d..121783e1da5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java @@ -19,18 +19,20 @@ */ package org.sonar.batch.issue.tracking; +import org.sonar.batch.util.BatchUtils; + +import org.sonar.batch.bootstrap.WSLoader; import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; -import org.sonar.batch.bootstrap.ServerClient; public class DefaultServerLineHashesLoader implements ServerLineHashesLoader { - private final ServerClient server; + private final WSLoader wsLoader; - public DefaultServerLineHashesLoader(ServerClient server) { - this.server = server; + public DefaultServerLineHashesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; } @Override @@ -44,7 +46,7 @@ public class DefaultServerLineHashesLoader implements ServerLineHashesLoader { .addContext("file", fileKey) .startDebug("Load line hashes"); try { - return server.request("/api/sources/hash?key=" + ServerClient.encodeForUrl(fileKey)); + return wsLoader.loadString("/api/sources/hash?key=" + BatchUtils.encodeForUrl(fileKey)); } finally { profiler.stopDebug(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index c553aeb349a..370c3f32ca9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -190,7 +190,7 @@ public class BatchMediumTester { } TaskBuilder builder = new TaskBuilder(this); builder.property("sonar.projectBaseDir", sonarProps.getParentFile().getAbsolutePath()); - for (Map.Entry entry : prop.entrySet()) { + for (Map.Entry entry : prop.entrySet()) { builder.property(entry.getKey().toString(), entry.getValue().toString()); } return builder; @@ -238,7 +238,7 @@ public class BatchMediumTester { return ref.globalSettings(); } - public FakeGlobalRepositoriesLoader add(Metric metric) { + public FakeGlobalRepositoriesLoader add(Metric metric) { Boolean optimizedBestValue = metric.isOptimizedBestValue(); ref.metrics().add(new org.sonar.batch.protocol.input.Metric(metricId, metric.key(), diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java index 6acf8f31cb2..b49448be10c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java @@ -19,9 +19,10 @@ */ package org.sonar.batch.phases; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.sonar.api.batch.SensorContext; import org.sonar.api.resources.Project; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.events.BatchStepEvent; import org.sonar.batch.events.EventBus; import org.sonar.batch.index.DefaultIndex; @@ -48,7 +49,7 @@ public final class PhaseExecutor { private final QProfileVerifier profileVerifier; private final IssueExclusionsLoader issueExclusionsLoader; private final IssuesReports issuesReport; - private final DefaultAnalysisMode analysisMode; + private final ProjectAnalysisMode analysisMode; private final LocalIssueTracking localIssueTracking; public PhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java index fb6075057dd..2435330b613 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java @@ -19,8 +19,12 @@ */ package org.sonar.batch.report; +import org.sonar.batch.scan.ProjectAnalysisMode; + +import org.sonar.batch.util.BatchUtils; import com.github.kevinsawicki.http.HttpRequest; import com.google.common.annotations.VisibleForTesting; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -28,6 +32,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.Date; + import org.apache.commons.io.FileUtils; import org.picocontainer.Startable; import org.slf4j.Logger; @@ -39,11 +44,9 @@ import org.sonar.api.config.Settings; import org.sonar.api.platform.Server; import org.sonar.api.utils.TempFolder; import org.sonar.api.utils.ZipUtils; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.protocol.output.BatchReportWriter; import org.sonar.batch.scan.ImmutableProjectReactor; - import static java.lang.String.format; @BatchSide @@ -57,7 +60,7 @@ public class ReportPublisher implements Startable { private final Server server; private final Settings settings; private final ImmutableProjectReactor projectReactor; - private final DefaultAnalysisMode analysisMode; + private final ProjectAnalysisMode analysisMode; private final TempFolder temp; private ReportPublisherStep[] publishers; @@ -66,7 +69,7 @@ public class ReportPublisher implements Startable { private BatchReportWriter writer; public ReportPublisher(Settings settings, ServerClient serverClient, Server server, - ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) { + ImmutableProjectReactor projectReactor, ProjectAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) { this.serverClient = serverClient; this.server = server; this.projectReactor = projectReactor; @@ -134,7 +137,7 @@ public class ReportPublisher implements Startable { void sendOrDumpReport(File report) { ProjectDefinition projectDefinition = projectReactor.getRoot(); String effectiveKey = projectDefinition.getKeyWithBranch(); - String relativeUrl = "/api/computation/submit_report?projectKey=" + effectiveKey + "&projectName=" + ServerClient.encodeForUrl(projectDefinition.getName()); + String relativeUrl = "/api/computation/submit_report?projectKey=" + effectiveKey + "&projectName=" + BatchUtils.encodeForUrl(projectDefinition.getName()); String dumpDirLocation = settings.getString(DUMP_REPORT_PROP_KEY); if (dumpDirLocation == null) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java index 428e9bccab5..beb47749d6f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java @@ -19,22 +19,23 @@ */ package org.sonar.batch.repository; -import org.sonar.batch.bootstrap.ServerClient; +import org.sonar.batch.bootstrap.WSLoader; + import org.sonar.batch.protocol.input.GlobalRepositories; public class DefaultGlobalRepositoriesLoader implements GlobalRepositoriesLoader { private static final String BATCH_GLOBAL_URL = "/batch/global"; - private final ServerClient serverClient; + private final WSLoader wsLoader; - public DefaultGlobalRepositoriesLoader(ServerClient serverClient) { - this.serverClient = serverClient; + public DefaultGlobalRepositoriesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; } @Override public GlobalRepositories load() { - return GlobalRepositories.fromJson(serverClient.request(BATCH_GLOBAL_URL)); + return GlobalRepositories.fromJson(wsLoader.loadString(BATCH_GLOBAL_URL)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java index 71f4953769e..da008967662 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java @@ -19,11 +19,13 @@ */ package org.sonar.batch.repository; +import org.sonar.batch.scan.ProjectAnalysisMode; + +import org.sonar.batch.util.BatchUtils; +import org.sonar.batch.bootstrap.WSLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.bootstrap.ProjectReactor; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.bootstrap.AnalysisProperties; import org.sonar.batch.protocol.input.ProjectRepositories; import org.sonar.batch.rule.ModuleQProfiles; @@ -34,25 +36,25 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad private static final String BATCH_PROJECT_URL = "/batch/project"; - private final ServerClient serverClient; - private final DefaultAnalysisMode analysisMode; + private final WSLoader wsLoader; + private final ProjectAnalysisMode analysisMode; - public DefaultProjectRepositoriesLoader(ServerClient serverClient, DefaultAnalysisMode analysisMode) { - this.serverClient = serverClient; + public DefaultProjectRepositoriesLoader(WSLoader wsLoader, ProjectAnalysisMode analysisMode) { + this.wsLoader = wsLoader; this.analysisMode = analysisMode; } @Override public ProjectRepositories load(ProjectReactor reactor, AnalysisProperties taskProperties) { String projectKey = reactor.getRoot().getKeyWithBranch(); - String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey); + String url = BATCH_PROJECT_URL + "?key=" + BatchUtils.encodeForUrl(projectKey); if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) { LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server."); - url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP)); + url += "&profile=" + BatchUtils.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP)); } url += "&preview=" + analysisMode.isPreview(); - return ProjectRepositories.fromJson(serverClient.request(url)); + return ProjectRepositories.fromJson(wsLoader.loadString(url)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java index e71e9eb54cb..b6d9723bb55 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java @@ -19,35 +19,32 @@ */ package org.sonar.batch.repository; +import org.sonar.batch.util.BatchUtils; + +import com.google.common.io.ByteSource; +import org.sonar.batch.bootstrap.WSLoader; import com.google.common.base.Function; -import com.google.common.io.InputSupplier; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.protocol.input.BatchInput.ServerIssue; -import org.sonar.api.utils.HttpDownloader; import java.io.IOException; import java.io.InputStream; public class DefaultServerIssuesLoader implements ServerIssuesLoader { - private final ServerClient serverClient; + private final WSLoader wsLoader; - public DefaultServerIssuesLoader(ServerClient serverClient) { - this.serverClient = serverClient; + public DefaultServerIssuesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; } @Override public void load(String componentKey, Function consumer, boolean incremental) { - try { - InputSupplier request = serverClient.doRequest("/batch/issues?key=" + ServerClient.encodeForUrl(componentKey), "GET", null); - parseIssues(request, consumer); - } catch (HttpDownloader.HttpException e) { - throw serverClient.handleHttpException(e); - } + ByteSource request = wsLoader.loadSource("/batch/issues?key=" + BatchUtils.encodeForUrl(componentKey)); + parseIssues(request, consumer); } - private static void parseIssues(InputSupplier input, Function consumer) { - try (InputStream is = input.getInput()) { + private static void parseIssues(ByteSource input, Function consumer) { + try (InputStream is = input.openStream()) { ServerIssue previousIssue = ServerIssue.parseDelimitedFrom(is); while (previousIssue != null) { consumer.apply(previousIssue); diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepository.java b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepository.java index 7208086c308..c6f1d8ae58c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepository.java @@ -19,12 +19,13 @@ */ package org.sonar.batch.repository.user; +import org.sonar.batch.util.BatchUtils; + +import org.sonar.batch.bootstrap.WSLoader; +import com.google.common.io.ByteSource; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Lists; -import com.google.common.io.InputSupplier; -import org.sonar.api.utils.HttpDownloader; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.protocol.input.BatchInput; import java.io.IOException; @@ -36,10 +37,10 @@ import java.util.List; public class UserRepository { - private ServerClient serverClient; + private WSLoader wsLoader; - public UserRepository(ServerClient serverClient) { - this.serverClient = serverClient; + public UserRepository(WSLoader wsLoader) { + this.wsLoader = wsLoader; } public Collection loadFromWs(List userLogins) { @@ -47,25 +48,20 @@ public class UserRepository { return Collections.emptyList(); } - try { - InputSupplier request = serverClient.doRequest("/batch/users?logins=" + Joiner.on(',').join(Lists.transform(userLogins, new Function() { - @Override - public String apply(String input) { - return ServerClient.encodeForUrl(input); - } - })), "GET", null); - - return parseUsers(request); + ByteSource source = wsLoader.loadSource("/batch/users?logins=" + Joiner.on(',').join(Lists.transform(userLogins, new Function() { + @Override + public String apply(String input) { + return BatchUtils.encodeForUrl(input); + } + }))); - } catch (HttpDownloader.HttpException e) { - throw serverClient.handleHttpException(e); - } + return parseUsers(source); } - private static Collection parseUsers(InputSupplier input) { + private static Collection parseUsers(ByteSource input) { List users = new ArrayList<>(); - try (InputStream is = input.getInput()) { + try (InputStream is = input.openStream()) { BatchInput.User user = BatchInput.User.parseDelimitedFrom(is); while (user != null) { users.add(user); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java index bf1d0815ede..cf5a3c44fd9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java @@ -20,12 +20,13 @@ package org.sonar.batch.scan; import com.google.common.collect.Lists; + import java.util.List; + import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.config.Settings; import org.sonar.api.utils.MessageException; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.GlobalSettings; import org.sonar.batch.protocol.input.ProjectRepositories; @@ -35,10 +36,10 @@ import org.sonar.batch.protocol.input.ProjectRepositories; public class ModuleSettings extends Settings { private final ProjectRepositories projectReferentials; - private DefaultAnalysisMode analysisMode; + private ProjectAnalysisMode analysisMode; public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition moduleDefinition, ProjectRepositories projectReferentials, - DefaultAnalysisMode analysisMode) { + ProjectAnalysisMode analysisMode) { super(batchSettings.getDefinitions()); this.projectReferentials = projectReferentials; this.analysisMode = analysisMode; diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultAnalysisMode.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectAnalysisMode.java similarity index 80% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultAnalysisMode.java rename to sonar-batch/src/main/java/org/sonar/batch/scan/ProjectAnalysisMode.java index c6b70402b95..1f544864cbe 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultAnalysisMode.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectAnalysisMode.java @@ -17,7 +17,9 @@ * 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.batch.bootstrap; +package org.sonar.batch.scan; + +import org.sonar.batch.bootstrap.AnalysisProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,21 +33,27 @@ import java.util.Map; /** * @since 4.0 */ -public class DefaultAnalysisMode implements AnalysisMode { +public class ProjectAnalysisMode implements AnalysisMode { - private static final Logger LOG = LoggerFactory.getLogger(DefaultAnalysisMode.class); + private static final Logger LOG = LoggerFactory.getLogger(ProjectAnalysisMode.class); private boolean preview; private boolean incremental; + private boolean quick; private boolean mediumTestMode; - public DefaultAnalysisMode(Map props) { - init(props); + public ProjectAnalysisMode(AnalysisProperties props) { + init(props.properties()); } @Override public boolean isPreview() { - return preview || incremental; + return preview || incremental || quick; + } + + @Override + public boolean isQuick() { + return quick; } @Override @@ -66,20 +74,19 @@ public class DefaultAnalysisMode implements AnalysisMode { String mode = props.get(CoreProperties.ANALYSIS_MODE); preview = CoreProperties.ANALYSIS_MODE_PREVIEW.equals(mode); incremental = CoreProperties.ANALYSIS_MODE_INCREMENTAL.equals(mode); + quick = CoreProperties.ANALYSIS_MODE_QUICK.equals(mode); } mediumTestMode = "true".equals(props.get(BatchMediumTester.MEDIUM_TEST_ENABLED)); if (incremental) { LOG.info("Incremental mode"); } else if (preview) { LOG.info("Preview mode"); + } else if (quick) { + LOG.info("Quick mode"); } if (mediumTestMode) { LOG.info("Medium test mode"); } - // To stay compatible with plugins that use the old property to check mode - if (incremental || preview) { - props.put(CoreProperties.DRY_RUN, "true"); - } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 06d0a4423a2..1e0b5d4571b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -34,7 +34,6 @@ import org.sonar.batch.DefaultFileLinesContextFactory; import org.sonar.batch.DefaultProjectTree; import org.sonar.batch.ProjectConfigurator; import org.sonar.batch.bootstrap.AnalysisProperties; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.ExtensionInstaller; import org.sonar.batch.bootstrap.ExtensionMatcher; import org.sonar.batch.bootstrap.ExtensionUtils; @@ -83,7 +82,6 @@ public class ProjectScanContainer extends ComponentContainer { private static final Logger LOG = Loggers.get(ProjectScanContainer.class); - private final DefaultAnalysisMode analysisMode; private final Object[] components; private final AnalysisProperties props; @@ -91,7 +89,6 @@ public class ProjectScanContainer extends ComponentContainer { super(globalContainer); this.props = props; this.components = components; - analysisMode = globalContainer.getComponentByType(DefaultAnalysisMode.class); } @Override @@ -123,6 +120,7 @@ public class ProjectScanContainer extends ComponentContainer { private void addBatchComponents() { add( props, + ProjectAnalysisMode.class, projectReactorBuilder(), new MutableProjectReactorProvider(getComponentByType(ProjectBootstrapper.class)), new ImmutableProjectReactorProvider(), @@ -134,6 +132,7 @@ public class ProjectScanContainer extends ComponentContainer { ProjectExclusions.class, ProjectReactorValidator.class, new ProjectRepositoriesProvider(), + new WSLoaderProjectProvider(), DefaultResourceCreationLock.class, CodeColorizers.class, MetricProvider.class, @@ -206,7 +205,7 @@ public class ProjectScanContainer extends ComponentContainer { LOG.debug("Start recursive analysis of project modules"); DefaultProjectTree tree = getComponentByType(DefaultProjectTree.class); scanRecursively(tree.getRootProject()); - if (analysisMode.isMediumTest()) { + if (getComponentByType(ProjectAnalysisMode.class).isMediumTest()) { getComponentByType(ScanTaskObservers.class).notifyEndOfScanTask(); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectSettings.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectSettings.java index 14720cbc944..5066ccfc524 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectSettings.java @@ -20,13 +20,14 @@ package org.sonar.batch.scan; import com.google.common.collect.ImmutableMap; + import java.util.Map; + import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.utils.MessageException; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.DroppedPropertyChecker; import org.sonar.batch.bootstrap.GlobalSettings; import org.sonar.batch.protocol.input.ProjectRepositories; @@ -43,10 +44,10 @@ public class ProjectSettings extends Settings { private final GlobalSettings globalSettings; private final ProjectRepositories projectRepositories; - private final DefaultAnalysisMode mode; + private final ProjectAnalysisMode mode; public ProjectSettings(ProjectReactor reactor, GlobalSettings globalSettings, PropertyDefinitions propertyDefinitions, - ProjectRepositories projectRepositories, DefaultAnalysisMode mode) { + ProjectRepositories projectRepositories, ProjectAnalysisMode mode) { super(propertyDefinitions); this.mode = mode; getEncryption().setPathToSecretKey(globalSettings.getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/WSLoaderProjectProvider.java b/sonar-batch/src/main/java/org/sonar/batch/scan/WSLoaderProjectProvider.java new file mode 100644 index 00000000000..164354efb29 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/WSLoaderProjectProvider.java @@ -0,0 +1,57 @@ +/* + * 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.batch.scan; + +import org.picocontainer.injectors.ProviderAdapter; + +import java.util.Map; + +import org.sonar.batch.bootstrap.AnalysisProperties; +import org.sonar.batch.bootstrap.ServerClient; +import org.sonar.batch.bootstrap.WSLoader; +import org.sonar.home.cache.PersistentCache; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; + +public class WSLoaderProjectProvider extends ProviderAdapter { + private WSLoader wsLoader; + + public WSLoader provide(AnalysisProperties props, AnalysisMode mode, PersistentCache cache, ServerClient client) { + if (wsLoader == null) { + cache.reconfigure(); + wsLoader = new WSLoader(isCacheEnabled(props.properties(), mode.isPreview()), cache, client); + wsLoader.setStrategy(getStrategy(mode)); + } + return wsLoader; + } + + private static LoadStrategy getStrategy(AnalysisMode mode) { + if (mode.isQuick()) { + return LoadStrategy.CACHE_FIRST; + } + + return LoadStrategy.SERVER_FIRST; + } + + private static boolean isCacheEnabled(Map props, boolean isPreview) { + String enableOffline = props.get("sonar.enableOffline"); + return isPreview && "true".equals(enableOffline); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java index 317612d9f69..f7296e97de5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.scan.filesystem; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; @@ -28,7 +30,6 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.config.Settings; import org.sonar.api.scan.filesystem.PathResolver; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import javax.annotation.CheckForNull; @@ -43,12 +44,12 @@ class InputFileBuilder { private final LanguageDetection langDetection; private final StatusDetection statusDetection; private final DefaultModuleFileSystem fs; - private final DefaultAnalysisMode analysisMode; + private final ProjectAnalysisMode analysisMode; private final Settings settings; private final FileMetadata fileMetadata; InputFileBuilder(String moduleKey, PathResolver pathResolver, LanguageDetection langDetection, - StatusDetection statusDetection, DefaultModuleFileSystem fs, DefaultAnalysisMode analysisMode, Settings settings, FileMetadata fileMetadata) { + StatusDetection statusDetection, DefaultModuleFileSystem fs, ProjectAnalysisMode analysisMode, Settings settings, FileMetadata fileMetadata) { this.moduleKey = moduleKey; this.pathResolver = pathResolver; this.langDetection = langDetection; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java index 304e330e7c1..3e9d31fbd89 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java @@ -19,12 +19,13 @@ */ package org.sonar.batch.scan.filesystem; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.sonar.api.batch.BatchSide; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.config.Settings; import org.sonar.api.scan.filesystem.PathResolver; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; @BatchSide public class InputFileBuilderFactory { @@ -33,12 +34,12 @@ public class InputFileBuilderFactory { private final PathResolver pathResolver; private final LanguageDetectionFactory langDetectionFactory; private final StatusDetectionFactory statusDetectionFactory; - private final DefaultAnalysisMode analysisMode; + private final ProjectAnalysisMode analysisMode; private final Settings settings; private final FileMetadata fileMetadata; public InputFileBuilderFactory(ProjectDefinition def, PathResolver pathResolver, LanguageDetectionFactory langDetectionFactory, - StatusDetectionFactory statusDetectionFactory, DefaultAnalysisMode analysisMode, Settings settings, FileMetadata fileMetadata) { + StatusDetectionFactory statusDetectionFactory, ProjectAnalysisMode analysisMode, Settings settings, FileMetadata fileMetadata) { this.fileMetadata = fileMetadata; this.moduleKey = def.getKeyWithBranch(); this.pathResolver = pathResolver; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/report/IssuesReports.java b/sonar-batch/src/main/java/org/sonar/batch/scan/report/IssuesReports.java index eaa566bddfd..9aaf25528c6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/report/IssuesReports.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/report/IssuesReports.java @@ -19,16 +19,17 @@ */ package org.sonar.batch.scan.report; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.sonar.api.batch.BatchSide; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; @BatchSide public class IssuesReports { - private final DefaultAnalysisMode analysisMode; + private final ProjectAnalysisMode analysisMode; private final Reporter[] reporters; - public IssuesReports(DefaultAnalysisMode analysisMode, Reporter... reporters) { + public IssuesReports(ProjectAnalysisMode analysisMode, Reporter... reporters) { this.reporters = reporters; this.analysisMode = analysisMode; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java index e6321ac8b3b..b5460a6e2a1 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java +++ b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java @@ -19,6 +19,9 @@ */ package org.sonar.batch.util; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + import org.apache.commons.lang.StringUtils; public class BatchUtils { @@ -34,4 +37,13 @@ public class BatchUtils { String cleanKey = StringUtils.deleteWhitespace(projectKey); return StringUtils.replace(cleanKey, ":", "_"); } + + public static String encodeForUrl(String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Encoding not supported", e); + } + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java index c8d2e367475..78995ef7a74 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -50,9 +50,10 @@ public class BatchPluginInstallerTest { @Test public void listRemotePlugins() { - ServerClient server = mock(ServerClient.class); - when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle\nsqale"); - BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + WSLoader wsLoader = mock(WSLoader.class); + when(wsLoader.load("/deploy/plugins/index.txt")).thenReturn("checkstyle\nsqale".getBytes()); + when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn("checkstyle\nsqale"); + BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, fileCache, pluginPredicate); List remotePlugins = installer.listRemotePlugins(); assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); @@ -63,8 +64,8 @@ public class BatchPluginInstallerTest { File pluginJar = temp.newFile(); when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); - ServerClient server = mock(ServerClient.class); - BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + WSLoader wsLoader = mock(WSLoader.class); + BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, fileCache, pluginPredicate); RemotePlugin remote = new RemotePlugin("checkstyle").setFile("checkstyle-plugin.jar", "fakemd5_1"); File file = installer.download(remote); @@ -76,9 +77,9 @@ public class BatchPluginInstallerTest { public void should_fail_to_get_plugin_index() { thrown.expect(IllegalStateException.class); - ServerClient server = mock(ServerClient.class); - doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); + WSLoader wsLoader = mock(WSLoader.class); + doThrow(new IllegalStateException()).when(wsLoader).load("/deploy/plugins/index.txt"); - new BatchPluginInstaller(server, fileCache, pluginPredicate).installRemotes(); + new BatchPluginInstaller(wsLoader, fileCache, pluginPredicate).installRemotes(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java index 511d29d9244..937281d2fba 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -22,7 +22,6 @@ package org.sonar.batch.bootstrap; import org.junit.Test; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,7 +29,7 @@ import static org.mockito.Mockito.when; public class BatchPluginPredicateTest { Settings settings = new Settings(); - DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class); + GlobalMode mode = mock(GlobalMode.class); @Test public void accept_if_no_inclusions_nor_exclusions() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java index b01040b29cd..aef314012ca 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java @@ -40,7 +40,7 @@ import static org.mockito.Mockito.when; public class ExtensionInstallerTest { - DefaultAnalysisMode mode; + GlobalMode mode; BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); private static Plugin newPluginInstance(final Object... extensions) { @@ -53,7 +53,7 @@ public class ExtensionInstallerTest { @Before public void setUp() { - mode = mock(DefaultAnalysisMode.class); + mode = mock(GlobalMode.class); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java new file mode 100644 index 00000000000..fb174d9d506 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java @@ -0,0 +1,67 @@ +/* + * 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.batch.bootstrap; + +import org.sonar.api.CoreProperties; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GlobalModeTest { + @Test + public void testQuick() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_QUICK); + assertThat(mode.isPreview()).isTrue(); + } + + @Test + public void testPreview() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PREVIEW); + assertThat(mode.isPreview()).isTrue(); + } + + @Test + public void testIncremental() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_INCREMENTAL); + assertThat(mode.isPreview()).isTrue(); + } + + @Test + public void testOtherProperty() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ANALYSIS); + assertThat(mode.isPreview()).isFalse(); + } + + @Test + public void testDeprecatedDryRun() { + GlobalMode mode = createMode(CoreProperties.DRY_RUN, "true"); + assertThat(mode.isPreview()).isTrue(); + } + + private GlobalMode createMode(String key, String value) { + Map map = new HashMap<>(); + map.put(key, value); + BootstrapProperties props = new BootstrapProperties(map); + return new GlobalMode(props); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java index 18f205ad772..b48ebc78db4 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java @@ -19,7 +19,11 @@ */ package org.sonar.batch.bootstrap; +import org.sonar.api.CoreProperties; + import java.util.Collections; + +import static org.mockito.Mockito.when; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -28,7 +32,6 @@ import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.batch.protocol.input.GlobalRepositories; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -43,13 +46,13 @@ public class GlobalSettingsTest { GlobalRepositories globalRef; BootstrapProperties bootstrapProps; - private DefaultAnalysisMode mode; + private GlobalMode mode; @Before public void prepare() { globalRef = new GlobalRepositories(); bootstrapProps = new BootstrapProperties(Collections.emptyMap()); - mode = mock(DefaultAnalysisMode.class); + mode = mock(GlobalMode.class); } @Test @@ -60,6 +63,13 @@ public class GlobalSettingsTest { assertThat(batchSettings.getBoolean("sonar.cpd.cross")).isTrue(); } + + @Test + public void support_deprecated_dry_run() { + when(mode.isPreview()).thenReturn(true); + GlobalSettings batchSettings = new GlobalSettings(bootstrapProps, new PropertyDefinitions(), globalRef, mode); + assertThat(batchSettings.getString(CoreProperties.DRY_RUN)).isEqualTo("true"); + } @Test public void should_log_warn_msg_for_each_jdbc_property_if_present() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java new file mode 100644 index 00000000000..757e3536717 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java @@ -0,0 +1,118 @@ +/* + * 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.batch.bootstrap; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.apache.commons.io.IOUtils.write; + +public class MockHttpServer { + private Server server; + private String responseBody; + private String requestBody; + private String mockResponseData; + private int mockResponseStatus = SC_OK; + private int numRequests = 0; + + public void start() throws Exception { + server = new Server(0); + server.setHandler(getMockHandler()); + server.start(); + } + + public int getNumberRequests() { + return numRequests; + } + + /** + * Creates an {@link org.mortbay.jetty.handler.AbstractHandler handler} returning an arbitrary String as a response. + * + * @return never null. + */ + public Handler getMockHandler() { + Handler handler = new AbstractHandler() { + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + numRequests++; + setResponseBody(getMockResponseData()); + setRequestBody(IOUtils.toString(baseRequest.getInputStream())); + response.setStatus(mockResponseStatus); + response.setContentType("text/xml;charset=utf-8"); + write(getResponseBody(), response.getOutputStream()); + baseRequest.setHandled(true); + } + }; + return handler; + } + + public void stop() { + try { + if (server != null) { + server.stop(); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to stop HTTP server", e); + } + } + + public String getResponseBody() { + return responseBody; + } + + public void setResponseBody(String responseBody) { + this.responseBody = responseBody; + } + + public String getRequestBody() { + return requestBody; + } + + public void setRequestBody(String requestBody) { + this.requestBody = requestBody; + } + + public void setMockResponseStatus(int status) { + this.mockResponseStatus = status; + } + + public String getMockResponseData() { + return mockResponseData; + } + + public void setMockResponseData(String mockResponseData) { + this.mockResponseData = mockResponseData; + } + + public int getPort() { + return server.getConnectors()[0].getLocalPort(); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PersistentCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PersistentCacheProviderTest.java index 813c5efce70..4857621a910 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PersistentCacheProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PersistentCacheProviderTest.java @@ -45,19 +45,4 @@ public class PersistentCacheProviderTest { public void test_cache_dir() { assertThat(provider.provide(props).getBaseDirectory().toFile()).exists().isDirectory(); } - - @Test - public void test_enableCache() { - // normally force update (cache disabled) - assertThat(provider.provide(props).isForceUpdate()).isTrue(); - - props.properties().put("sonar.enableHttpCache", "true"); - provider = new PersistentCacheProvider(); - assertThat(provider.provide(props).isForceUpdate()).isFalse(); - } - - @Test - public void test_reconfigure() { - - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java index 75538ff5b73..56133fe40d0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java @@ -19,30 +19,18 @@ */ package org.sonar.batch.bootstrap; +import org.sonar.batch.util.BatchUtils; + import java.io.File; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.io.IOUtils; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; + import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.batch.bootstrapper.EnvironmentInformation; -import org.sonar.home.cache.PersistentCache; -import org.sonar.home.cache.PersistentCacheBuilder; - -import static javax.servlet.http.HttpServletResponse.SC_OK; -import static org.apache.commons.io.IOUtils.write; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -53,19 +41,10 @@ public class ServerClientTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @Rule - public TemporaryFolder cacheTmp = new TemporaryFolder(); - @Rule public ExpectedException thrown = ExpectedException.none(); private MockHttpServer server = null; private BootstrapProperties bootstrapProps = mock(BootstrapProperties.class); - private DefaultAnalysisMode mode = null; - - @Before - public void setUp() { - mode = mock(DefaultAnalysisMode.class); - when(mode.isPreview()).thenReturn(true); - } @After public void stopServer() { @@ -74,78 +53,30 @@ public class ServerClientTest { } } - @Test - public void dont_cache_post_request() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - - assertThat(newServerClient().request("/foo", "POST")).isEqualTo("this is the content"); - - // cache never accessed, so not even the .lock should be there - assertThat(getNumFilesInCache()).isEqualTo(0); - } - - @Test - public void dont_cache_non_preview_mode() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - - when(mode.isPreview()).thenReturn(false); - assertThat(newServerClient().request("/foo")).isEqualTo("this is the content"); - - // cache never accessed, so not even the .lock should be there - assertThat(getNumFilesInCache()).isEqualTo(0); - } - - @Test - public void cache_preview_mode() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - - assertThat(newServerClient().request("/foo")).isEqualTo("this is the content"); - - // should have the .lock and one request cached - assertThat(getNumFilesInCache()).isEqualTo(2); - } - @Test public void should_remove_url_ending_slash() { BootstrapProperties settings = mock(BootstrapProperties.class); when(settings.property("sonar.host.url")).thenReturn("http://localhost:8080/sonar/"); - - PersistentCache ps = new PersistentCacheBuilder(new Slf4jLogger()).setSonarHome(cacheTmp.getRoot().toPath()).build(); - ServerClient client = new ServerClient(settings, new EnvironmentInformation("Junit", "4"), ps, mode); + ServerClient client = new ServerClient(settings, new EnvironmentInformation("Junit", "4")); assertThat(client.getURL()).isEqualTo("http://localhost:8080/sonar"); } @Test public void should_request_url() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - - assertThat(newServerClient().request("/foo")).isEqualTo("this is the content"); + startServer(null, "this is the content"); + assertThat(newServerClient().downloadString("/foo")).isEqualTo("this is the content"); } @Test public void should_escape_html_from_url() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - - assertThat(newServerClient().request("/")).isEqualTo("this is the content"); + startServer(null, "this is the content"); + assertThat(newServerClient().downloadString("/")).isEqualTo("this is the content"); } @Test public void should_download_file() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseData("this is the content"); - + startServer(null, "this is the content"); File file = temp.newFile(); newServerClient().download("/foo", file); assertThat(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)).isEqualTo("this is the content"); @@ -153,138 +84,55 @@ public class ServerClientTest { @Test public void should_fail_if_unauthorized_with_no_login_password() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseStatus(401); - + startServer(401, null); thrown.expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password."); - newServerClient().request("/foo"); + newServerClient().downloadString("/foo"); } @Test public void should_fail_if_unauthorized_with_login_password_provided() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseStatus(401); + startServer(401, null); when(bootstrapProps.property(eq("sonar.login"))).thenReturn("login"); when(bootstrapProps.property(eq("sonar.password"))).thenReturn("password"); thrown.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password"); - newServerClient().request("/foo"); + newServerClient().downloadString("/foo"); } @Test public void should_display_json_error_when_403() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseStatus(403); - server.setMockResponseData("{\"errors\":[{\"msg\":\"Insufficient privileges\"}]}"); - + startServer(403, "{\"errors\":[{\"msg\":\"Insufficient privileges\"}]}"); thrown.expectMessage("Insufficient privileges"); - newServerClient().request("/foo"); + newServerClient().downloadString("/foo"); } @Test public void should_fail_if_error() throws Exception { - server = new MockHttpServer(); - server.start(); - server.setMockResponseStatus(500); - + startServer(500, null); thrown.expectMessage("Fail to execute request [code=500, url=http://localhost:" + server.getPort() + "/foo]"); - newServerClient().request("/foo"); + newServerClient().downloadString("/foo"); } @Test - public void testEncode() { - assertThat(ServerClient.encodeForUrl("my value")).isEqualTo("my+value"); + public void string_encode() { + assertThat(BatchUtils.encodeForUrl("my value")).isEqualTo("my+value"); } private ServerClient newServerClient() { when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort()); - PersistentCache ps = new PersistentCacheBuilder(new Slf4jLogger()).setSonarHome(cacheTmp.getRoot().toPath()).build(); - return new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4"), ps, mode); + return new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4")); } - private int getNumFilesInCache() { - return new File(cacheTmp.getRoot(), "ws_cache").listFiles().length; - } - - static class MockHttpServer { - private Server server; - private String responseBody; - private String requestBody; - private String mockResponseData; - private int mockResponseStatus = SC_OK; - - public void start() throws Exception { - server = new Server(0); - server.setHandler(getMockHandler()); - server.start(); - } - - /** - * Creates an {@link org.mortbay.jetty.handler.AbstractHandler handler} returning an arbitrary String as a response. - * - * @return never null. - */ - public Handler getMockHandler() { - Handler handler = new AbstractHandler() { - - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - setResponseBody(getMockResponseData()); - setRequestBody(IOUtils.toString(baseRequest.getInputStream())); - response.setStatus(mockResponseStatus); - response.setContentType("text/xml;charset=utf-8"); - write(getResponseBody(), response.getOutputStream()); - baseRequest.setHandled(true); - } - }; - return handler; - } - - public void stop() { - try { - if (server != null) { - server.stop(); - } - } catch (Exception e) { - throw new IllegalStateException("Fail to stop HTTP server", e); - } - } - - public String getResponseBody() { - return responseBody; - } - - public void setResponseBody(String responseBody) { - this.responseBody = responseBody; - } - - public String getRequestBody() { - return requestBody; - } - - public void setRequestBody(String requestBody) { - this.requestBody = requestBody; - } - - public void setMockResponseStatus(int status) { - this.mockResponseStatus = status; - } - - public String getMockResponseData() { - return mockResponseData; - } - - public void setMockResponseData(String mockResponseData) { - this.mockResponseData = mockResponseData; + private void startServer(Integer responseStatus, String responseData) throws Exception { + server = new MockHttpServer(); + server.start(); + + if (responseStatus != null) { + server.setMockResponseStatus(responseStatus); } - - public int getPort() { - return server.getConnectors()[0].getLocalPort(); + if (responseData != null) { + server.setMockResponseData(responseData); } - } - } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderGlobalProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderGlobalProviderTest.java new file mode 100644 index 00000000000..8cc8b118018 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderGlobalProviderTest.java @@ -0,0 +1,76 @@ +/* + * 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.batch.bootstrap; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.when; + +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; +import org.junit.Test; +import org.junit.Before; +import org.sonar.home.cache.PersistentCache; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class WSLoaderGlobalProviderTest { + @Mock + private PersistentCache cache; + + @Mock + private ServerClient client; + + @Mock + private GlobalMode mode; + + private WSLoaderGlobalProvider loaderProvider; + private Map propMap; + private BootstrapProperties props; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + loaderProvider = new WSLoaderGlobalProvider(); + } + + @Test + public void testDefault() { + propMap = new HashMap<>(); + props = new BootstrapProperties(propMap); + + WSLoader wsLoader = loaderProvider.provide(props, mode, cache, client); + assertThat(wsLoader.getStrategy()).isEqualTo(LoadStrategy.SERVER_FIRST); + assertThat(wsLoader.isCacheEnabled()).isEqualTo(false); + } + + @Test + public void testOffline() { + propMap = new HashMap<>(); + propMap.put("sonar.enableOffline", "true"); + when(mode.isPreview()).thenReturn(true); + props = new BootstrapProperties(propMap); + + WSLoader wsLoader = loaderProvider.provide(props, mode, cache, client); + assertThat(wsLoader.isCacheEnabled()).isEqualTo(true); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java new file mode 100644 index 00000000000..c4571c9e8e6 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java @@ -0,0 +1,214 @@ +/* + * 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.batch.bootstrap; + +import org.sonar.api.utils.HttpDownloader; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.apache.commons.io.IOUtils; +import org.mockito.Mockito; +import org.mockito.InOrder; + +import java.io.IOException; +import java.net.URI; + +import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyInt; +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; +import org.junit.Test; +import org.mockito.MockitoAnnotations; +import org.junit.Before; +import org.sonar.home.cache.PersistentCache; +import org.mockito.Mock; + +public class WSLoaderTest { + private final static String ID = "/dummy"; + private final static String cacheValue = "cache"; + private final static String serverValue = "server"; + + @Mock + private ServerClient client; + @Mock + private PersistentCache cache; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenReturn(IOUtils.toInputStream(serverValue)); + when(cache.get(ID, null)).thenReturn(cacheValue.getBytes()); + when(client.getURI(anyString())).thenAnswer(new Answer() { + @Override + public URI answer(InvocationOnMock invocation) throws Throwable { + return new URI((String) invocation.getArguments()[0]); + } + }); + } + + @Test + public void dont_retry_server() throws IOException { + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException()); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.setCacheEnabled(true); + + assertThat(loader.loadString(ID)).isEqualTo(cacheValue); + assertThat(loader.loadString(ID)).isEqualTo(cacheValue); + + // only try once the server + verify(client, times(1)).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt()); + verify(cache, times(2)).get(ID, null); + } + + @Test + public void test_cache_strategy_fallback() throws IOException { + when(cache.get(ID, null)).thenReturn(null); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + loader.setCacheEnabled(true); + + loader.load(ID); + + InOrder inOrder = Mockito.inOrder(client, cache); + inOrder.verify(cache).get(ID, null); + inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt()); + } + + @Test + public void test_server_strategy_fallback() throws IOException { + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException()); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.setCacheEnabled(true); + assertThat(loader.loadString(ID)).isEqualTo(cacheValue); + + InOrder inOrder = Mockito.inOrder(client, cache); + inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt()); + inOrder.verify(cache).get(ID, null); + } + + @Test + public void test_put_cache() throws IOException { + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.load(ID); + verify(cache).put(ID, serverValue.getBytes()); + } + + @Test(expected = NullPointerException.class) + public void test_throw_cache_exception_fallback() throws IOException { + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException()); + when(cache.get(ID, null)).thenThrow(new NullPointerException()); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.setCacheEnabled(true); + + loader.load(ID); + } + + @Test(expected = IllegalStateException.class) + public void test_throw_cache_exception() throws IOException { + when(cache.get(ID, null)).thenThrow(new IllegalStateException()); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + loader.setCacheEnabled(true); + + loader.load(ID); + } + + @Test(expected = IllegalStateException.class) + public void test_throw_http_exceptions() { + HttpDownloader.HttpException httpException = mock(HttpDownloader.HttpException.class); + IllegalStateException wrapperException = new IllegalStateException(httpException); + + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(wrapperException); + + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + + try { + loader.load(ID); + } catch(IllegalStateException e) { + // cache should not be used + verifyNoMoreInteractions(cache); + throw e; + } + } + + @Test + public void test_server_not_accessible() throws IOException { + when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException()); + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.load(ID); + loader.load(ID); + + // only try once from server + verify(client, times(1)).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt()); + verify(cache, times(2)).get(ID, null); + } + + @Test + public void test_change_strategy() throws IOException { + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + test_cache_strategy_fallback(); + } + + @Test + public void test_enable_cache() throws IOException { + WSLoader loader = new WSLoader(true, cache, client); + loader.setCacheEnabled(false); + test_cache_disabled(); + } + + @Test + public void test_server_strategy() throws IOException { + WSLoader loader = new WSLoader(true, cache, client); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + loader.load(ID); + + // should not fetch from cache + verify(cache).put(ID, serverValue.getBytes()); + verifyNoMoreInteractions(cache); + } + + @Test + public void test_cache_disabled() throws IOException { + WSLoader loader = new WSLoader(cache, client); + loader.load(ID); + + // should not even put + verifyNoMoreInteractions(cache); + } + + @Test + public void test_string() { + WSLoader loader = new WSLoader(cache, client); + assertThat(loader.loadString(ID)).isEqualTo(serverValue); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTestWithServer.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTestWithServer.java new file mode 100644 index 00000000000..5b5f2c7efee --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTestWithServer.java @@ -0,0 +1,112 @@ +/* + * 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.batch.bootstrap; + +import static org.mockito.Mockito.mock; +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.home.cache.PersistentCache; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class WSLoaderTestWithServer { + private static final String RESPONSE_STRING = "this is the content"; + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MockHttpServer server; + private PersistentCache cache; + private ServerClient client; + private WSLoader loader; + + @Before + public void setUp() throws Exception { + server = new MockHttpServer(); + server.start(); + + BootstrapProperties bootstrapProps = mock(BootstrapProperties.class); + when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort()); + + client = new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4")); + cache = new PersistentCache(temp.getRoot().toPath(), 1000 * 60, new Slf4jLogger()); + loader = new WSLoader(cache, client); + } + + @After + public void tearDown() { + if (server != null) { + server.stop(); + } + } + + @Test + public void testServer() { + loader.setCacheEnabled(false); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + server.setMockResponseData(RESPONSE_STRING); + assertThat(loader.loadString("/foo")).isEqualTo(RESPONSE_STRING); + } + + @Test + public void testCacheDisabled() { + loader.setCacheEnabled(false); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + makeRequests(); + assertThat(server.getNumberRequests()).isEqualTo(3); + } + + @Test + public void testCacheEnabled() { + loader.setCacheEnabled(true); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + makeRequests(); + assertThat(server.getNumberRequests()).isEqualTo(1); + } + + @Test + public void testServerStrategy() { + loader.setCacheEnabled(true); + loader.setStrategy(LoadStrategy.SERVER_FIRST); + makeRequests(); + assertThat(server.getNumberRequests()).isEqualTo(3); + } + + @Test + public void testCacheStrategyDisabled() { + loader.setCacheEnabled(false); + loader.setStrategy(LoadStrategy.CACHE_FIRST); + makeRequests(); + assertThat(server.getNumberRequests()).isEqualTo(3); + } + + private void makeRequests() { + server.setMockResponseData(RESPONSE_STRING); + assertThat(loader.loadString("/foo")).isEqualTo(RESPONSE_STRING); + assertThat(loader.loadString("/foo")).isEqualTo(RESPONSE_STRING); + assertThat(loader.loadString("/foo")).isEqualTo(RESPONSE_STRING); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/cpd/index/IndexFactoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/cpd/index/IndexFactoryTest.java index 07279e5396b..af5a763c26d 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cpd/index/IndexFactoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cpd/index/IndexFactoryTest.java @@ -19,14 +19,14 @@ */ package org.sonar.batch.cpd.index; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -38,13 +38,13 @@ public class IndexFactoryTest { Settings settings; IndexFactory factory; Logger logger; - private DefaultAnalysisMode analysisMode; + private ProjectAnalysisMode analysisMode; @Before public void setUp() { project = new Project("foo"); settings = new Settings(); - analysisMode = mock(DefaultAnalysisMode.class); + analysisMode = mock(ProjectAnalysisMode.class); factory = new IndexFactory(analysisMode, settings); logger = mock(Logger.class); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java index 28d9b251ec9..991e61dc463 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java @@ -19,12 +19,13 @@ */ package org.sonar.batch.issue.tracking; +import org.sonar.batch.bootstrap.WSLoader; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.HttpDownloader; -import org.sonar.batch.bootstrap.ServerClient; import java.net.URI; import java.net.URISyntaxException; @@ -46,32 +47,32 @@ public class DefaultServerLineHashesLoaderTest { @Test public void should_download_source_from_ws_if_preview_mode() { - ServerClient server = mock(ServerClient.class); - when(server.request(anyString())).thenReturn("ae12\n\n43fb"); + WSLoader wsLoader = mock(WSLoader.class); + when(wsLoader.loadString(anyString())).thenReturn("ae12\n\n43fb"); - ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(server); + ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(wsLoader); String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Bar.c"); assertThat(hashes).containsOnly("ae12", "", "43fb"); - verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FBar.c"); + verify(wsLoader).loadString("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FBar.c"); } @Test public void should_download_source_with_space_from_ws_if_preview_mode() { - ServerClient server = mock(ServerClient.class); - when(server.request(anyString())).thenReturn("ae12\n\n43fb"); + WSLoader server = mock(WSLoader.class); + when(server.loadString(anyString())).thenReturn("ae12\n\n43fb"); ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(server); String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Foo Bar.c"); assertThat(hashes).containsOnly("ae12", "", "43fb"); - verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c"); + verify(server).loadString("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c"); } @Test public void should_fail_to_download_source_from_ws() throws URISyntaxException { - ServerClient server = mock(ServerClient.class); - when(server.request(anyString())).thenThrow(new HttpDownloader.HttpException(new URI(""), 500)); + WSLoader server = mock(WSLoader.class); + when(server.loadString(anyString())).thenThrow(new HttpDownloader.HttpException(new URI(""), 500)); ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(server); diff --git a/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java b/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java index ea301e425b0..df097d68457 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.report; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; @@ -27,23 +29,21 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.config.Settings; import org.sonar.api.platform.Server; import org.sonar.api.utils.TempFolder; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.scan.ImmutableProjectReactor; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class ReportPublisherTest { - private DefaultAnalysisMode mode; + private ProjectAnalysisMode mode; private ImmutableProjectReactor reactor; @Before public void setUp() { - mode = mock(DefaultAnalysisMode.class); + mode = mock(ProjectAnalysisMode.class); reactor = mock(ImmutableProjectReactor.class); when(reactor.getRoot()).thenReturn(ProjectDefinition.create().setKey("struts")); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java index 9458bc31c35..c9cadbe05ee 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java @@ -19,16 +19,16 @@ */ package org.sonar.batch.repository; +import org.sonar.batch.scan.ProjectAnalysisMode; + +import org.sonar.batch.bootstrap.WSLoader; import com.google.common.collect.Maps; import org.junit.Before; import org.junit.Test; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.batch.bootstrap.AnalysisProperties; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.rule.ModuleQProfiles; - import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -38,18 +38,18 @@ import static org.mockito.Mockito.when; public class DefaultProjectRepositoriesLoaderTest { private DefaultProjectRepositoriesLoader loader; - private ServerClient serverClient; - private DefaultAnalysisMode analysisMode; + private WSLoader wsLoader; + private ProjectAnalysisMode analysisMode; private ProjectReactor reactor; private AnalysisProperties taskProperties; @Before public void prepare() { - serverClient = mock(ServerClient.class); - analysisMode = mock(DefaultAnalysisMode.class); - loader = new DefaultProjectRepositoriesLoader(serverClient, analysisMode); + wsLoader = mock(WSLoader.class); + analysisMode = mock(ProjectAnalysisMode.class); + loader = new DefaultProjectRepositoriesLoader(wsLoader, analysisMode); loader = spy(loader); - when(serverClient.request(anyString())).thenReturn("{}"); + when(wsLoader.loadString(anyString())).thenReturn("{}"); taskProperties = new AnalysisProperties(Maps.newHashMap(), ""); } @@ -58,18 +58,18 @@ public class DefaultProjectRepositoriesLoaderTest { reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo")); when(analysisMode.isPreview()).thenReturn(false); loader.load(reactor, taskProperties); - verify(serverClient).request("/batch/project?key=foo&preview=false"); + verify(wsLoader).loadString("/batch/project?key=foo&preview=false"); when(analysisMode.isPreview()).thenReturn(true); loader.load(reactor, taskProperties); - verify(serverClient).request("/batch/project?key=foo&preview=true"); + verify(wsLoader).loadString("/batch/project?key=foo&preview=true"); } @Test public void passAndEncodeProjectKeyParameter() { reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr")); loader.load(reactor, taskProperties); - verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false"); + verify(wsLoader).loadString("/batch/project?key=foo+b%C3%A0r&preview=false"); } @Test @@ -77,7 +77,7 @@ public class DefaultProjectRepositoriesLoaderTest { reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo")); taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2"); loader.load(reactor, taskProperties); - verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false"); + verify(wsLoader).loadString("/batch/project?key=foo&profile=my-profile%232&preview=false"); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java index 4cc756ff1ce..d1686d63372 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java @@ -19,17 +19,17 @@ */ package org.sonar.batch.repository; +import com.google.common.io.ByteSource; + +import org.sonar.batch.bootstrap.WSLoader; import com.google.common.base.Function; -import com.google.common.io.InputSupplier; import org.junit.Before; import org.junit.Test; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.protocol.input.BatchInput; import org.sonar.batch.protocol.input.BatchInput.ServerIssue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -39,18 +39,18 @@ import static org.mockito.Mockito.when; public class DefaultServerIssuesLoaderTest { private DefaultServerIssuesLoader loader; - private ServerClient serverClient; + private WSLoader wsLoader; @Before public void prepare() { - serverClient = mock(ServerClient.class); - loader = new DefaultServerIssuesLoader(serverClient); + wsLoader = mock(WSLoader.class); + loader = new DefaultServerIssuesLoader(wsLoader); } @Test public void loadFromWs() throws Exception { - InputSupplier is = mock(InputSupplier.class); - when(serverClient.doRequest("/batch/issues?key=foo", "GET", null)).thenReturn(is); + ByteSource bs = mock(ByteSource.class); + when(wsLoader.loadSource("/batch/issues?key=foo")).thenReturn(bs); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -59,7 +59,7 @@ public class DefaultServerIssuesLoaderTest { ServerIssue.newBuilder().setKey("ab2").build() .writeDelimitedTo(bos); - when(is.getInput()).thenReturn(new ByteArrayInputStream(bos.toByteArray())); + when(bs.openStream()).thenReturn(new ByteArrayInputStream(bos.toByteArray())); final List result = new ArrayList<>(); loader.load("foo", new Function() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryTest.java index 54d62bce5f8..6910f2dc462 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryTest.java @@ -19,15 +19,15 @@ */ package org.sonar.batch.repository.user; -import com.google.common.io.InputSupplier; +import com.google.common.io.ByteSource; + +import org.sonar.batch.bootstrap.WSLoader; import org.junit.Test; -import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.protocol.input.BatchInput; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @@ -39,18 +39,17 @@ public class UserRepositoryTest { @Test public void testLoad() throws IOException { - ServerClient serverClient = mock(ServerClient.class); - UserRepository userRepo = new UserRepository(serverClient); + WSLoader wsLoader = mock(WSLoader.class); + UserRepository userRepo = new UserRepository(wsLoader); ByteArrayOutputStream out = new ByteArrayOutputStream(); BatchInput.User.Builder builder = BatchInput.User.newBuilder(); builder.setLogin("fmallet").setName("Freddy Mallet").build().writeDelimitedTo(out); builder.setLogin("sbrandhof").setName("Simon").build().writeDelimitedTo(out); - InputSupplier is = mock(InputSupplier.class); - when(serverClient.doRequest("/batch/users?logins=fmallet,sbrandhof", "GET", null)) - .thenReturn(is); - when(is.getInput()).thenReturn(new ByteArrayInputStream(out.toByteArray())); + ByteSource source = mock(ByteSource.class); + when(wsLoader.loadSource("/batch/users?logins=fmallet,sbrandhof")).thenReturn(source); + when(source.openStream()).thenReturn(new ByteArrayInputStream(out.toByteArray())); assertThat(userRepo.loadFromWs(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon")); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultAnalysisModeTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/DefaultAnalysisModeTest.java similarity index 60% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultAnalysisModeTest.java rename to sonar-batch/src/test/java/org/sonar/batch/scan/DefaultAnalysisModeTest.java index 65c7d548b9a..a2b20592b68 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultAnalysisModeTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/DefaultAnalysisModeTest.java @@ -17,14 +17,15 @@ * 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.batch.bootstrap; +package org.sonar.batch.scan; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; +import org.sonar.batch.bootstrap.AnalysisProperties; +import org.sonar.batch.scan.ProjectAnalysisMode; import org.junit.Test; import org.sonar.api.CoreProperties; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -33,12 +34,12 @@ public class DefaultAnalysisModeTest { @Test public void regular_analysis_by_default() { - DefaultAnalysisMode mode = new DefaultAnalysisMode(Collections.emptyMap()); + ProjectAnalysisMode mode = new ProjectAnalysisMode(new AnalysisProperties(Collections.emptyMap())); assertThat(mode.isPreview()).isFalse(); assertThat(mode.isIncremental()).isFalse(); - mode = new DefaultAnalysisMode(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, "pouet")); + mode = createMode(CoreProperties.ANALYSIS_MODE, "pouet"); assertThat(mode.isPreview()).isFalse(); assertThat(mode.isIncremental()).isFalse(); @@ -46,7 +47,7 @@ public class DefaultAnalysisModeTest { @Test public void support_analysis_mode() { - DefaultAnalysisMode mode = new DefaultAnalysisMode(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ANALYSIS)); + ProjectAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ANALYSIS); assertThat(mode.isPreview()).isFalse(); assertThat(mode.isIncremental()).isFalse(); @@ -54,31 +55,41 @@ public class DefaultAnalysisModeTest { @Test public void support_preview_mode() { - Map props = Maps.newHashMap(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PREVIEW)); - DefaultAnalysisMode mode = new DefaultAnalysisMode(props); + ProjectAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PREVIEW); assertThat(mode.isPreview()).isTrue(); assertThat(mode.isIncremental()).isFalse(); + } + + @Test + public void support_quick_mode() { + ProjectAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_QUICK); - assertThat(props.get(CoreProperties.DRY_RUN)).isEqualTo("true"); + assertThat(mode.isPreview()).isTrue(); + assertThat(mode.isIncremental()).isFalse(); + assertThat(mode.isQuick()).isTrue(); } @Test public void support_incremental_mode() { - Map props = Maps.newHashMap(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_INCREMENTAL)); - DefaultAnalysisMode mode = new DefaultAnalysisMode(props); + ProjectAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_INCREMENTAL); assertThat(mode.isPreview()).isTrue(); assertThat(mode.isIncremental()).isTrue(); - - assertThat(props.get(CoreProperties.DRY_RUN)).isEqualTo("true"); } @Test public void support_deprecated_dryrun_property() { - DefaultAnalysisMode mode = new DefaultAnalysisMode(Maps.newHashMap(ImmutableMap.of(CoreProperties.DRY_RUN, "true"))); + ProjectAnalysisMode mode = createMode(CoreProperties.DRY_RUN, "true"); assertThat(mode.isPreview()).isTrue(); assertThat(mode.isIncremental()).isFalse(); } + + private ProjectAnalysisMode createMode(String key, String value) { + Map map = new HashMap<>(); + map.put(key, value); + + return new ProjectAnalysisMode(new AnalysisProperties(map)); + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java index abf12abc034..4edec303f85 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java @@ -27,7 +27,6 @@ import org.junit.rules.ExpectedException; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.utils.MessageException; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.GlobalSettings; import org.sonar.batch.protocol.input.ProjectRepositories; @@ -43,12 +42,12 @@ public class ModuleSettingsTest { public ExpectedException thrown = ExpectedException.none(); ProjectRepositories projectRef; - private DefaultAnalysisMode mode; + private ProjectAnalysisMode mode; @Before public void before() { projectRef = new ProjectRepositories(); - mode = mock(DefaultAnalysisMode.class); + mode = mock(ProjectAnalysisMode.class); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java index 0970ede0908..5b4e60f4bc0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java @@ -19,8 +19,12 @@ */ package org.sonar.batch.scan; +import org.sonar.batch.bootstrap.GlobalMode; + import com.google.common.collect.ImmutableMap; + import java.util.Collections; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -33,11 +37,9 @@ import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.batch.bootstrap.BootstrapProperties; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import org.sonar.batch.bootstrap.GlobalSettings; import org.sonar.batch.protocol.input.GlobalRepositories; import org.sonar.batch.protocol.input.ProjectRepositories; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -53,13 +55,15 @@ public class ProjectSettingsTest { ProjectDefinition project = ProjectDefinition.create().setKey("struts"); GlobalSettings bootstrapProps; - private DefaultAnalysisMode mode; + private GlobalMode globalMode; + private ProjectAnalysisMode mode; @Before public void prepare() { projectRef = new ProjectRepositories(); - mode = mock(DefaultAnalysisMode.class); - bootstrapProps = new GlobalSettings(new BootstrapProperties(Collections.emptyMap()), new PropertyDefinitions(), new GlobalRepositories(), mode); + globalMode = mock(GlobalMode.class); + mode = mock(ProjectAnalysisMode.class); + bootstrapProps = new GlobalSettings(new BootstrapProperties(Collections.emptyMap()), new PropertyDefinitions(), new GlobalRepositories(), globalMode); } @Test @@ -118,7 +122,7 @@ public class ProjectSettingsTest { @Test public void should_log_a_warning_when_a_dropper_property_is_present() { - GlobalSettings settings = new GlobalSettings(new BootstrapProperties(ImmutableMap.of("sonar.qualitygate", "somevalue")), new PropertyDefinitions(), new GlobalRepositories(), mode); + GlobalSettings settings = new GlobalSettings(new BootstrapProperties(ImmutableMap.of("sonar.qualitygate", "somevalue")), new PropertyDefinitions(), new GlobalRepositories(), globalMode); new ProjectSettings(new ProjectReactor(project), settings, new PropertyDefinitions(), projectRef, mode); assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Property 'sonar.qualitygate' is not supported any more. It will be ignored."); diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/WSLoaderProjectProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/WSLoaderProjectProviderTest.java new file mode 100644 index 00000000000..f3a4aa62216 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/WSLoaderProjectProviderTest.java @@ -0,0 +1,81 @@ +/* + * 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.batch.scan; + +import org.sonar.api.batch.AnalysisMode; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.batch.bootstrap.AnalysisProperties; +import org.sonar.batch.bootstrap.ServerClient; +import org.sonar.batch.bootstrap.WSLoader; +import org.sonar.batch.bootstrap.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.when; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WSLoaderProjectProviderTest { + @Mock + private PersistentCache cache; + + @Mock + private ServerClient client; + + @Mock + private AnalysisMode mode; + + private WSLoaderProjectProvider loaderProvider; + private Map propMap; + private AnalysisProperties props; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + loaderProvider = new WSLoaderProjectProvider(); + propMap = new HashMap<>(); + } + + @Test + public void testDefault() { + props = new AnalysisProperties(propMap, null); + + WSLoader loader = loaderProvider.provide(props, mode, cache, client); + assertThat(loader.getStrategy()).isEqualTo(LoadStrategy.SERVER_FIRST); + assertThat(loader.isCacheEnabled()).isEqualTo(false); + } + + @Test + public void testSingleMode() { + when(mode.isQuick()).thenReturn(true); + when(mode.isPreview()).thenReturn(true); + propMap.put("sonar.enableOffline", "true"); + props = new AnalysisProperties(propMap, null); + + WSLoader loader = loaderProvider.provide(props, mode, cache, client); + assertThat(loader.getStrategy()).isEqualTo(LoadStrategy.CACHE_FIRST); + assertThat(loader.isCacheEnabled()).isEqualTo(true); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java index 60f85a52e17..2f5141d676e 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java @@ -19,14 +19,14 @@ */ package org.sonar.batch.scan.filesystem; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.junit.Test; import org.mockito.Mockito; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.config.Settings; import org.sonar.api.scan.filesystem.PathResolver; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -37,7 +37,7 @@ public class InputFileBuilderFactoryTest { LanguageDetectionFactory langDetectionFactory = mock(LanguageDetectionFactory.class, Mockito.RETURNS_MOCKS); StatusDetectionFactory statusDetectionFactory = mock(StatusDetectionFactory.class, Mockito.RETURNS_MOCKS); DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); - DefaultAnalysisMode analysisMode = mock(DefaultAnalysisMode.class); + ProjectAnalysisMode analysisMode = mock(ProjectAnalysisMode.class); InputFileBuilderFactory factory = new InputFileBuilderFactory(ProjectDefinition.create().setKey("struts"), pathResolver, langDetectionFactory, statusDetectionFactory, analysisMode, new Settings(), new FileMetadata()); diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java index ff304db2e22..5b14b2bc9be 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.scan.filesystem; +import org.sonar.batch.scan.ProjectAnalysisMode; + import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; @@ -29,7 +31,6 @@ import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.config.Settings; import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.api.utils.PathUtils; -import org.sonar.batch.bootstrap.DefaultAnalysisMode; import java.io.File; import java.nio.charset.StandardCharsets; @@ -47,7 +48,7 @@ public class InputFileBuilderTest { LanguageDetection langDetection = mock(LanguageDetection.class); StatusDetection statusDetection = mock(StatusDetection.class); DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); - DefaultAnalysisMode analysisMode = mock(DefaultAnalysisMode.class); + ProjectAnalysisMode analysisMode = mock(ProjectAnalysisMode.class); @Test public void complete_input_file() throws Exception { diff --git a/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java b/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java index a1bcbee4601..b8b74d6afab 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java +++ b/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java @@ -19,6 +19,7 @@ */ package org.sonar.core.util; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Strings; @@ -28,6 +29,7 @@ import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.common.io.Files; import com.google.common.io.InputSupplier; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -42,7 +44,9 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; + import javax.annotation.Nullable; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -60,13 +64,19 @@ import org.sonar.api.utils.log.Loggers; public class DefaultHttpDownloader extends HttpDownloader { private final BaseHttpDownloader downloader; private final Integer readTimeout; + private final Integer connectTimeout; public DefaultHttpDownloader(Server server, Settings settings) { this(server, settings, null); } public DefaultHttpDownloader(Server server, Settings settings, @Nullable Integer readTimeout) { + this(server, settings, null, readTimeout); + } + + public DefaultHttpDownloader(Server server, Settings settings, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) { this.readTimeout = readTimeout; + this.connectTimeout = connectTimeout; downloader = new BaseHttpDownloader(settings.getProperties(), server.getVersion()); } @@ -75,7 +85,12 @@ public class DefaultHttpDownloader extends HttpDownloader { } public DefaultHttpDownloader(Settings settings, @Nullable Integer readTimeout) { + this(settings, null, readTimeout); + } + + public DefaultHttpDownloader(Settings settings, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) { this.readTimeout = readTimeout; + this.connectTimeout = connectTimeout; downloader = new BaseHttpDownloader(settings.getProperties(), null); } @@ -97,7 +112,7 @@ public class DefaultHttpDownloader extends HttpDownloader { @Override protected String readString(URI uri, Charset charset) { try { - return CharStreams.toString(CharStreams.newReaderSupplier(downloader.newInputSupplier(uri, this.readTimeout), charset)); + return CharStreams.toString(CharStreams.newReaderSupplier(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout), charset)); } catch (IOException e) { throw failToDownload(uri, e); } @@ -111,7 +126,7 @@ public class DefaultHttpDownloader extends HttpDownloader { @Override public byte[] download(URI uri) { try { - return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.readTimeout)); + return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout)); } catch (IOException e) { throw failToDownload(uri, e); } @@ -124,7 +139,7 @@ public class DefaultHttpDownloader extends HttpDownloader { @Override public InputStream openStream(URI uri) { try { - return downloader.newInputSupplier(uri, this.readTimeout).getInput(); + return downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(); } catch (IOException e) { throw failToDownload(uri, e); } @@ -133,7 +148,7 @@ public class DefaultHttpDownloader extends HttpDownloader { @Override public void download(URI uri, File toFile) { try { - Files.copy(downloader.newInputSupplier(uri, this.readTimeout), toFile); + Files.copy(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout), toFile); } catch (IOException e) { FileUtils.deleteQuietly(toFile); throw failToDownload(uri, e); @@ -213,18 +228,29 @@ public class DefaultHttpDownloader extends HttpDownloader { } public InputSupplier newInputSupplier(URI uri) { - return new HttpInputSupplier(uri, GET, userAgent, null, null, TIMEOUT_MILLISECONDS); + return newInputSupplier(uri, GET, null, null, null, null); } public InputSupplier newInputSupplier(URI uri, @Nullable Integer readTimeoutMillis) { return newInputSupplier(uri, GET, readTimeoutMillis); } + /** + * @since 5.2 + */ + public InputSupplier newInputSupplier(URI uri, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) { + return newInputSupplier(uri, GET, connectTimeoutMillis, readTimeoutMillis); + } + + /** + * @since 5.2 + */ + public InputSupplier newInputSupplier(URI uri, String requestMethod, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) { + return newInputSupplier(uri, requestMethod, null, null, connectTimeoutMillis, readTimeoutMillis); + } + public InputSupplier newInputSupplier(URI uri, String requestMethod, @Nullable Integer readTimeoutMillis) { - if (readTimeoutMillis != null) { - return new HttpInputSupplier(uri, requestMethod, userAgent, null, null, readTimeoutMillis); - } - return new HttpInputSupplier(uri, requestMethod, userAgent, null, null, TIMEOUT_MILLISECONDS); + return newInputSupplier(uri, requestMethod, null, null, null, readTimeoutMillis); } public InputSupplier newInputSupplier(URI uri, String login, String password) { @@ -235,7 +261,7 @@ public class DefaultHttpDownloader extends HttpDownloader { * @since 5.0 */ public InputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password) { - return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, TIMEOUT_MILLISECONDS); + return newInputSupplier(uri, requestMethod, login, password, null, null); } public InputSupplier newInputSupplier(URI uri, String login, String password, @Nullable Integer readTimeoutMillis) { @@ -246,10 +272,17 @@ public class DefaultHttpDownloader extends HttpDownloader { * @since 5.0 */ public InputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password, @Nullable Integer readTimeoutMillis) { - if (readTimeoutMillis != null) { - return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, readTimeoutMillis); - } - return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, TIMEOUT_MILLISECONDS); + return newInputSupplier(uri, requestMethod, login, password, null, readTimeoutMillis); + } + + /** + * @since 5.2 + */ + public InputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password, @Nullable Integer connectTimeoutMillis, + @Nullable Integer readTimeoutMillis) { + int read = readTimeoutMillis != null ? readTimeoutMillis : TIMEOUT_MILLISECONDS; + int connect = connectTimeoutMillis != null ? connectTimeoutMillis : TIMEOUT_MILLISECONDS; + return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, connect, read); } private static class HttpInputSupplier implements InputSupplier { @@ -257,22 +290,27 @@ public class DefaultHttpDownloader extends HttpDownloader { private final String password; private final URI uri; private final String userAgent; + private final int connectTimeoutMillis; private final int readTimeoutMillis; private final String requestMethod; - HttpInputSupplier(URI uri, String requestMethod, String userAgent, String login, String password, int readTimeoutMillis) { + HttpInputSupplier(URI uri, String requestMethod, String userAgent, String login, String password, int connectTimeoutMillis, int readTimeoutMillis) { this.uri = uri; this.requestMethod = requestMethod; this.userAgent = userAgent; this.login = login; this.password = password; this.readTimeoutMillis = readTimeoutMillis; + this.connectTimeoutMillis = connectTimeoutMillis; } + /** + * @throws IOException any I/O error, not limited to the network connection + * @throws HttpException if HTTP response code > 400 + */ @Override public InputStream getInput() throws IOException { Loggers.get(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri, ProxySelector.getDefault()) + ")"); - HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); connection.setRequestMethod(requestMethod); HttpsTrust.INSTANCE.trust(connection); @@ -283,7 +321,7 @@ public class DefaultHttpDownloader extends HttpDownloader { String encoded = Base64.encodeBase64String((login + ":" + password).getBytes(StandardCharsets.UTF_8)); connection.setRequestProperty("Authorization", "Basic " + encoded); } - connection.setConnectTimeout(TIMEOUT_MILLISECONDS); + connection.setConnectTimeout(connectTimeoutMillis); connection.setReadTimeout(readTimeoutMillis); connection.setUseCaches(true); connection.setInstanceFollowRedirects(true); diff --git a/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java b/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java index 8ca54546371..f19f42c5345 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java @@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Properties; import java.util.zip.GZIPOutputStream; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.AfterClass; @@ -51,7 +52,6 @@ import org.simpleframework.transport.connect.SocketConnection; import org.sonar.api.config.Settings; import org.sonar.api.platform.Server; import org.sonar.api.utils.SonarException; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; @@ -123,6 +123,25 @@ public class DefaultHttpDownloaderTest { } } + @Test(timeout = 10000) + public void readStringConnectTimeout() throws IOException, URISyntaxException { + // non routable address + String url = "http://10.255.255.1"; + + thrown.expect(new BaseMatcher() { + @Override + public boolean matches(Object ex) { + return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException; + } + + @Override + public void describeTo(Description arg0) { + } + }); + DefaultHttpDownloader downloader = new DefaultHttpDownloader(new Settings(), 10, 50000); + downloader.openStream(new URI(url)); + } + @Test public void downloadBytes() throws URISyntaxException { byte[] bytes = new DefaultHttpDownloader(new Settings()).readBytes(new URI(baseUrl)); diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java index 95b93e6ed27..e4c8bd02912 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java @@ -32,7 +32,6 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.concurrent.Callable; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -51,25 +50,18 @@ public class PersistentCache { // eviction strategy is to expire entries after modification once a time duration has elapsed private final long defaultDurationToExpireMs; - private boolean forceUpdate; private final Logger logger; - public PersistentCache(Path baseDir, long defaultDurationToExpireMs, boolean forceUpdate, Logger logger) { + public PersistentCache(Path baseDir, long defaultDurationToExpireMs, Logger logger) { this.baseDir = baseDir; this.defaultDurationToExpireMs = defaultDurationToExpireMs; this.logger = logger; - reconfigure(forceUpdate); + reconfigure(); logger.debug("cache: " + baseDir + ", default expiration time (ms): " + defaultDurationToExpireMs); } - public void reconfigure(boolean forceUpdate) { - this.forceUpdate = forceUpdate; - - if (forceUpdate) { - logger.debug("cache: forcing update"); - } - + public void reconfigure() { try { Files.createDirectories(baseDir); } catch (IOException e) { @@ -81,16 +73,12 @@ public class PersistentCache { return baseDir; } - public boolean isForceUpdate() { - return forceUpdate; - } - @CheckForNull - public synchronized String getString(@Nonnull String obj, @Nullable final Callable valueLoader) throws Exception { - byte[] cached = get(obj, new Callable() { + public synchronized String getString(@Nonnull String obj, @Nullable final PersistentCacheLoader valueLoader) throws IOException { + byte[] cached = get(obj, new PersistentCacheLoader() { @Override - public byte[] call() throws Exception { - String s = valueLoader.call(); + public byte[] get() throws IOException { + String s = valueLoader.get(); if (s != null) { return s.getBytes(ENCODING); } @@ -106,26 +94,23 @@ public class PersistentCache { } @CheckForNull - public synchronized byte[] get(@Nonnull String obj, @Nullable Callable valueLoader) throws Exception { + public synchronized byte[] get(@Nonnull String obj, @Nullable PersistentCacheLoader valueLoader) throws IOException { String key = getKey(obj); try { lock(); - if (!forceUpdate) { - byte[] cached = getCache(key); - if (cached != null) { - logger.debug("cache hit for " + obj + " -> " + key); - return cached; - } + byte[] cached = getCache(key); - logger.debug("cache miss for " + obj + " -> " + key); - } else { - logger.debug("cache force update for " + obj + " -> " + key); + if (cached != null) { + logger.debug("cache hit for " + obj + " -> " + key); + return cached; } + logger.debug("cache miss for " + obj + " -> " + key); + if (valueLoader != null) { - byte[] value = valueLoader.call(); + byte[] value = valueLoader.get(); if (value != null) { putCache(key, value); } @@ -138,6 +123,16 @@ public class PersistentCache { return null; } + public synchronized void put(@Nonnull String obj, @Nonnull byte[] value) throws IOException { + String key = getKey(obj); + try { + lock(); + putCache(key, value); + } finally { + unlock(); + } + } + /** * Deletes all cache entries */ diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java index e115f1cab64..f0fa1596917 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java @@ -28,7 +28,6 @@ public class PersistentCacheBuilder { private static final long DEFAULT_EXPIRE_DURATION = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS); private static final String DIR_NAME = "ws_cache"; - private boolean forceUpdate = false; private Path cachePath; private final Logger logger; @@ -41,7 +40,7 @@ public class PersistentCacheBuilder { setSonarHome(findHome()); } - return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, forceUpdate, logger); + return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger); } public PersistentCacheBuilder setSonarHome(@Nullable Path p) { @@ -51,11 +50,6 @@ public class PersistentCacheBuilder { return this; } - public PersistentCacheBuilder forceUpdate(boolean update) { - this.forceUpdate = update; - return this; - } - private static Path findHome() { String home = System.getenv("SONAR_USER_HOME"); diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java new file mode 100644 index 00000000000..ee906b4b67d --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java @@ -0,0 +1,26 @@ +/* + * 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.home.cache; + +import java.io.IOException; + +public interface PersistentCacheLoader { + T get() throws IOException; +} diff --git a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java index 09335dffc3b..62e105bf861 100644 --- a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java +++ b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java @@ -20,7 +20,6 @@ package org.sonar.home.cache; import java.io.File; -import java.util.concurrent.Callable; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Rule; @@ -42,7 +41,7 @@ public class PersistentCacheTest { @Before public void setUp() { - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, false, mock(Logger.class)); + cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class)); } @Test @@ -59,9 +58,9 @@ public class PersistentCacheTest { @Test public void testNullValue() throws Exception { // mocks have their methods returning null by default - Callable c = mock(Callable.class); + PersistentCacheLoader c = mock(PersistentCacheLoader.class); assertThat(cache.get(URI, c)).isNull(); - verify(c).call(); + verify(c).get(); assertCacheHit(false); } @@ -78,30 +77,17 @@ public class PersistentCacheTest { assertCacheHit(true); } - @Test - public void testForceUpdate() throws Exception { - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, true, mock(Logger.class)); - - assertCacheHit(false); - assertCacheHit(false); - assertCacheHit(false); - - // with forceUpdate, it should still have cached the last call - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, false, mock(Logger.class)); - assertCacheHit(true); - } - @Test public void testReconfigure() throws Exception { - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, true, mock(Logger.class)); - assertCacheHit(false); + cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class)); assertCacheHit(false); + assertCacheHit(true); File root = tmp.getRoot(); FileUtils.deleteQuietly(root); // should re-create cache directory and start using the cache - cache.reconfigure(false); + cache.reconfigure(); assertThat(root).exists(); assertCacheHit(false); @@ -111,7 +97,7 @@ public class PersistentCacheTest { @Test public void testExpiration() throws Exception { // negative time to make sure it is expired on the second call - cache = new PersistentCache(tmp.getRoot().toPath(), -100, false, mock(Logger.class)); + cache = new PersistentCache(tmp.getRoot().toPath(), -100, mock(Logger.class)); assertCacheHit(false); assertCacheHit(false); } @@ -122,11 +108,11 @@ public class PersistentCacheTest { assertThat(c.wasCalled).isEqualTo(!hit); } - private class CacheFillerString implements Callable { + private class CacheFillerString implements PersistentCacheLoader { public boolean wasCalled = false; @Override - public String call() throws Exception { + public String get() { wasCalled = true; return VALUE; } @@ -139,8 +125,8 @@ public class PersistentCacheTest { */ @Test(expected = ArithmeticException.class) public void testExceptions() throws Exception { - Callable c = mock(Callable.class); - when(c.call()).thenThrow(ArithmeticException.class); + PersistentCacheLoader c = mock(PersistentCacheLoader.class); + when(c.get()).thenThrow(ArithmeticException.class); cache.get(URI, c); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index 13c47e7ce0d..247b9097e5e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -446,6 +446,11 @@ public interface CoreProperties { * @since 4.0 */ String ANALYSIS_MODE_PREVIEW = "preview"; + + /** + * @since 5.2 + */ + String ANALYSIS_MODE_QUICK = "quick"; /** * @since 4.0 diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/AnalysisMode.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/AnalysisMode.java index 2fad14d30c3..37734df0917 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/AnalysisMode.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/AnalysisMode.java @@ -29,5 +29,7 @@ public interface AnalysisMode { boolean isPreview(); boolean isIncremental(); + + boolean isQuick(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java index 9d574bb86bd..8bec2b65ea4 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java @@ -230,6 +230,7 @@ public class SensorContextTester implements SensorContext { public static class MockAnalysisMode implements AnalysisMode { private boolean isIncremental = false; private boolean isPreview = false; + private boolean isSingle = false; @Override public boolean isIncremental() { @@ -248,6 +249,15 @@ public class SensorContextTester implements SensorContext { public void setPreview(boolean value) { this.isPreview = value; } + + @Override + public boolean isQuick() { + return this.isSingle; + } + + public void setSingle(boolean single) { + this.isSingle = single; + } } private static class InMemorySensorStorage implements SensorStorage { -- 2.39.5