From 92b68fdb6bca9347e153424fb4f79ceae400fc3d Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 2 Dec 2015 11:40:43 +0100 Subject: [PATCH] ws-client should not throw HttpException by default --- .../it/authorisation/AuthenticationTest.java | 6 +- .../analysis/AnalysisWSLoaderProvider.java | 4 +- .../batch/bootstrap/BatchPluginInstaller.java | 9 +- .../sonar/batch/bootstrap/BatchWsClient.java | 130 ++++++++++++++++ ...ovider.java => BatchWsClientProvider.java} | 13 +- .../batch/bootstrap/GlobalContainer.java | 2 +- .../bootstrap/WsClientLoggingInterceptor.java | 90 ----------- .../batch/cache/StrategyWSLoaderProvider.java | 4 +- .../java/org/sonar/batch/cache/WSLoader.java | 24 ++- .../sonar/batch/report/ReportPublisher.java | 49 +++--- .../DefaultProjectRepositoriesLoader.java | 4 + .../AnalysisWSLoaderProviderTest.java | 26 +--- .../bootstrap/BatchPluginInstallerTest.java | 11 +- ...st.java => BatchWsClientProviderTest.java} | 20 +-- .../batch/bootstrap/BatchWsClientTest.java | 142 ++++++++++++++++++ .../WsClientLoggingInterceptorTest.java | 129 ---------------- .../cache/StrategyWSLoaderProviderTest.java | 4 +- .../org/sonar/batch/cache/WSLoaderTest.java | 40 +++-- .../batch/report/ReportPublisherTest.java | 36 +---- .../DefaultProjectRepositoriesLoaderTest.java | 4 +- .../package-info.java => BaseResponse.java} | 18 ++- .../org/sonarqube/ws/client/BaseService.java | 8 +- .../sonarqube/ws/client/HttpConnector.java | 26 +--- .../sonarqube/ws/client/HttpException.java | 4 +- .../org/sonarqube/ws/client/HttpResponse.java | 19 ++- .../org/sonarqube/ws/client/HttpWsClient.java | 8 - .../sonarqube/ws/client/MockWsResponse.java | 24 ++- .../org/sonarqube/ws/client/WsClient.java | 3 - .../org/sonarqube/ws/client/WsConnector.java | 11 +- .../org/sonarqube/ws/client/WsResponse.java | 32 +++- .../ws/client/ce/ComputeEngineService.java | 44 ------ .../ws/client/ce/SubmitWsRequest.java | 71 --------- .../sonarqube/ws/client/BaseServiceTest.java | 21 ++- .../ws/client/HttpConnectorTest.java | 42 +----- .../ws/client/HttpExceptionTest.java | 4 +- 35 files changed, 493 insertions(+), 589 deletions(-) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{WsClientProvider.java => BatchWsClientProvider.java} (80%) delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java rename sonar-batch/src/test/java/org/sonar/batch/bootstrap/{WsClientProviderTest.java => BatchWsClientProviderTest.java} (80%) create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java delete mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java rename sonar-ws/src/main/java/org/sonarqube/ws/client/{ce/package-info.java => BaseResponse.java} (72%) delete mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java delete mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java diff --git a/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java b/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java index 7288287edbe..806b6935183 100644 --- a/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java +++ b/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java @@ -93,7 +93,7 @@ public class AuthenticationTest { // authenticate WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build()); WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); - assertThat(response.getContent()).isEqualTo("{\"valid\":true}"); + assertThat(response.content()).isEqualTo("{\"valid\":true}"); } @Test @@ -107,7 +107,7 @@ public class AuthenticationTest { WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); - assertThat(response.getContent()).isEqualTo("{\"valid\":true}"); + assertThat(response.content()).isEqualTo("{\"valid\":true}"); } /** @@ -126,7 +126,7 @@ public class AuthenticationTest { // authenticate WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build()); WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); - assertThat(response.getContent()).isEqualTo("{\"valid\":false}"); + assertThat(response.content()).isEqualTo("{\"valid\":false}"); } @Test diff --git a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java index 328478d3f87..7ca47ae787a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java @@ -21,15 +21,15 @@ package org.sonar.batch.analysis; import org.picocontainer.injectors.ProviderAdapter; import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.cache.WSLoader; import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.home.cache.PersistentCache; -import org.sonarqube.ws.client.WsClient; public class AnalysisWSLoaderProvider extends ProviderAdapter { private WSLoader wsLoader; - public WSLoader provide(AnalysisMode mode, PersistentCache cache, WsClient client) { + public WSLoader provide(AnalysisMode mode, PersistentCache cache, BatchWsClient client) { if (wsLoader == null) { // recreate cache directory if needed for this analysis cache.reconfigure(); 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 56b4199e8e6..950d3e9baf3 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 @@ -42,7 +42,6 @@ import org.sonar.core.platform.RemotePlugin; import org.sonar.core.platform.RemotePluginFile; import org.sonar.home.cache.FileCache; import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsResponse; import static java.lang.String.format; @@ -59,9 +58,9 @@ public class BatchPluginInstaller implements PluginInstaller { private final WSLoader wsLoader; private final FileCache fileCache; private final BatchPluginPredicate pluginPredicate; - private final WsClient wsClient; + private final BatchWsClient wsClient; - public BatchPluginInstaller(WSLoader wsLoader, WsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) { + public BatchPluginInstaller(WSLoader wsLoader, BatchWsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) { this.wsLoader = wsLoader; this.fileCache = fileCache; this.pluginPredicate = pluginPredicate; @@ -151,8 +150,8 @@ public class BatchPluginInstaller implements PluginInstaller { LOG.info("Download {}", filename); } - WsResponse response = wsClient.wsConnector().call(new GetRequest(url)); - try (InputStream stream = response.getContentStream()) { + WsResponse response = wsClient.call(new GetRequest(url)); + try (InputStream stream = response.contentStream()) { FileUtils.copyInputStreamToFile(stream, toFile); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java new file mode 100644 index 00000000000..16bac82b84f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java @@ -0,0 +1,130 @@ +/* + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsConnector; +import org.sonarqube.ws.client.WsRequest; +import org.sonarqube.ws.client.WsResponse; + +import static java.lang.String.format; + +public class BatchWsClient { + + private static final Logger LOG = Loggers.get(BatchWsClient.class); + + private final WsClient target; + private final boolean hasCredentials; + private final String publicBaseUrl; + + public BatchWsClient(WsClient target, boolean hasCredentials, @Nullable String publicBaseUrl) { + this.target = target; + this.hasCredentials = hasCredentials; + if (StringUtils.isBlank(publicBaseUrl)) { + this.publicBaseUrl = target.wsConnector().baseUrl(); + } else { + this.publicBaseUrl = publicBaseUrl.replaceAll("(/)+$", "") + "/"; + } + } + + /** + * @throws IllegalStateException if the request could not be executed due to + * a connectivity problem or timeout. Because networks can + * fail during an exchange, it is possible that the remote server + * accepted the request before the failure + * @throws HttpException if the response code is not in range [200..300) + */ + public WsResponse call(WsRequest request) { + Profiler profiler = Profiler.createIfDebug(LOG).start(); + WsResponse response = target.wsConnector().call(request); + profiler.stopDebug(format("%s %d %s", request.getMethod(), response.code(), response.requestUrl())); + failIfUnauthorized(response); + return response; + } + + public String baseUrl() { + return target.wsConnector().baseUrl(); + } + + /** + * The public URL is optionally configured on server. If not, then the regular {@link #baseUrl()} is returned. + * URL has a trailing slash. + * See https://jira.sonarsource.com/browse/SONAR-4239 + */ + public String publicBaseUrl() { + return publicBaseUrl; + } + + @VisibleForTesting + WsConnector wsConnector() { + return target.wsConnector(); + } + + private void failIfUnauthorized(WsResponse response) { + if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + if (hasCredentials) { + // credentials are not valid + throw MessageException.of(format("Not authorized. Please check the properties %s and %s.", + CoreProperties.LOGIN, CoreProperties.PASSWORD)); + } + // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048 + throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " + + "Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD)); + + } + if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) { + // SONAR-4397 Details are in response content + throw MessageException.of(tryParseAsJsonError(response.content())); + } + response.failIfNotSuccessful(); + } + + private static String tryParseAsJsonError(String responseContent) { + try { + JsonParser parser = new JsonParser(); + JsonObject obj = parser.parse(responseContent).getAsJsonObject(); + JsonArray errors = obj.getAsJsonArray("errors"); + List errorMessages = new ArrayList<>(); + for (JsonElement e : errors) { + errorMessages.add(e.getAsJsonObject().get("msg").getAsString()); + } + return Joiner.on(", ").join(errorMessages); + } catch (Exception e) { + return responseContent; + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java similarity index 80% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java index 78e50cc2ca2..a8ac402da35 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java @@ -25,22 +25,21 @@ import org.sonar.api.batch.BatchSide; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonarqube.ws.client.HttpConnector; import org.sonarqube.ws.client.HttpWsClient; -import org.sonarqube.ws.client.WsClient; import static java.lang.Integer.parseInt; import static java.lang.String.valueOf; import static org.apache.commons.lang.StringUtils.defaultIfBlank; @BatchSide -public class WsClientProvider extends ProviderAdapter { +public class BatchWsClientProvider extends ProviderAdapter { static final int CONNECT_TIMEOUT_MS = 5_000; static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout"; static final int DEFAULT_READ_TIMEOUT_SEC = 60; - private HttpWsClient wsClient; + private BatchWsClient wsClient; - public synchronized WsClient provide(final GlobalProperties settings, final EnvironmentInformation env) { + public synchronized BatchWsClient provide(final GlobalProperties settings, final EnvironmentInformation env) { if (wsClient == null) { String url = defaultIfBlank(settings.property("sonar.host.url"), CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE); HttpConnector.Builder builder = new HttpConnector.Builder(); @@ -48,15 +47,15 @@ public class WsClientProvider extends ProviderAdapter { // TODO proxy String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC)); + String login = defaultIfBlank(settings.property(CoreProperties.LOGIN), null); builder .readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000) .connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS) .userAgent(env.toString()) .url(url) - .credentials(settings.property(CoreProperties.LOGIN), settings.property(CoreProperties.PASSWORD)) - .interceptor(new WsClientLoggingInterceptor()); + .credentials(login, settings.property(CoreProperties.PASSWORD)); - wsClient = new HttpWsClient(builder.build()); + wsClient = new BatchWsClient(new HttpWsClient(builder.build()), login != null, settings.property(CoreProperties.SERVER_BASE_URL)); } return wsClient; } 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 40b5cacb9d1..0440645b5c3 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 @@ -91,7 +91,7 @@ public class GlobalContainer extends ComponentContainer { CachesManager.class, GlobalSettings.class, - new WsClientProvider(), + new BatchWsClientProvider(), DefaultServer.class, new GlobalTempFolderProvider(), DefaultHttpDownloader.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java deleted file mode 100644 index f94bf084ee9..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 com.google.common.base.Joiner; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.squareup.okhttp.Interceptor; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.CoreProperties; -import org.sonar.api.utils.MessageException; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; - -import static java.lang.String.format; - -public class WsClientLoggingInterceptor implements Interceptor { - - private static final Logger LOG = Loggers.get(WsClientLoggingInterceptor.class); - - @Override - public Response intercept(Chain chain) throws IOException { - Response response = logAndSendRequest(chain); - if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { - if (StringUtils.isBlank(response.request().header("Authorization"))) { - // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048 - throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " + - "Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD)); - } - // credentials are not valid - throw MessageException.of(format("Not authorized. Please check the properties %s and %s.", - CoreProperties.LOGIN, CoreProperties.PASSWORD)); - } - if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) { - // SONAR-4397 Details are in response content - throw MessageException.of(tryParseAsJsonError(response.body().string())); - } - return response; - } - - private Response logAndSendRequest(Chain chain) throws IOException { - Request request = chain.request(); - Response response; - Profiler profiler = Profiler.createIfDebug(LOG).startTrace(format("%s %s", request.method(), request.url())); - response = chain.proceed(request); - profiler.stopDebug(format("%s %d %s", request.method(), response.code(), request.url())); - return response; - } - - private static String tryParseAsJsonError(String responseContent) { - try { - JsonParser parser = new JsonParser(); - JsonObject obj = parser.parse(responseContent).getAsJsonObject(); - JsonArray errors = obj.getAsJsonArray("errors"); - List errorMessages = new ArrayList<>(); - for (JsonElement e : errors) { - errorMessages.add(e.getAsJsonObject().get("msg").getAsString()); - } - return Joiner.on(", ").join(errorMessages); - } catch (Exception e) { - return responseContent; - } - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java index e7c573753df..e6f7d6b00cb 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java @@ -20,9 +20,9 @@ package org.sonar.batch.cache; import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.home.cache.PersistentCache; -import org.sonarqube.ws.client.WsClient; public class StrategyWSLoaderProvider extends ProviderAdapter { private final LoadStrategy strategy; @@ -32,7 +32,7 @@ public class StrategyWSLoaderProvider extends ProviderAdapter { this.strategy = strategy; } - public WSLoader provide(PersistentCache cache, WsClient client) { + public WSLoader provide(PersistentCache cache, BatchWsClient client) { if (wsLoader == null) { wsLoader = new WSLoader(strategy, cache, client); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java index 69e7c14538f..9864e574f9b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java @@ -26,12 +26,13 @@ import java.nio.charset.StandardCharsets; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.io.IOUtils; +import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.home.cache.PersistentCache; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; -import org.sonarqube.ws.client.WsClient; import static org.sonar.batch.cache.WSLoader.ServerStatus.ACCESSIBLE; import static org.sonar.batch.cache.WSLoader.ServerStatus.NOT_ACCESSIBLE; @@ -50,7 +51,7 @@ public class WSLoader { } private final LoadStrategy defautLoadStrategy; - private final WsClient client; + private final BatchWsClient wsClient; private final PersistentCache cache; private ServerStatus serverStatus; @@ -58,7 +59,7 @@ public class WSLoader { @Override public String load(String id) throws IOException { GetRequest getRequest = new GetRequest(id); - try (Reader reader = client.wsConnector().call(getRequest).getContentReader()) { + try (Reader reader = wsClient.call(getRequest).contentReader()) { String str = IOUtils.toString(reader); try { cache.put(id, str.getBytes(StandardCharsets.UTF_8)); @@ -81,7 +82,7 @@ public class WSLoader { @Override public InputStream load(String id) throws IOException { GetRequest getRequest = new GetRequest(id); - try (InputStream is = client.wsConnector().call(getRequest).getContentStream()) { + try (InputStream is = wsClient.call(getRequest).contentStream()) { try { cache.put(id, is); } catch (IOException e) { @@ -111,11 +112,11 @@ public class WSLoader { } } - public WSLoader(LoadStrategy strategy, PersistentCache cache, WsClient client) { + public WSLoader(LoadStrategy strategy, PersistentCache cache, BatchWsClient wsClient) { this.defautLoadStrategy = strategy; this.serverStatus = UNKNOWN; this.cache = cache; - this.client = client; + this.wsClient = wsClient; } @Nonnull @@ -193,7 +194,7 @@ public class WSLoader { throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); } } - throw new IllegalStateException("Server is not available: " + client.wsConnector().baseUrl(), serverNotAvailable.getCause()); + throw new IllegalStateException("Server is not available: " + wsClient.baseUrl(), serverNotAvailable.getCause()); } } @@ -201,7 +202,6 @@ public class WSLoader { T load(String id) throws IOException; } - @Nonnull private WSLoaderResult loadFromCache(String id, DataLoader loader) throws NotAvailableException { T result; @@ -217,7 +217,6 @@ public class WSLoader { return new WSLoaderResult<>(result, true); } - @Nonnull private WSLoaderResult loadFromServer(String id, DataLoader loader) throws NotAvailableException { if (isOffline()) { throw new NotAvailableException("Server not available"); @@ -226,11 +225,10 @@ public class WSLoader { T t = loader.load(id); switchToOnline(); return new WSLoaderResult<>(t, false); + } catch (HttpException | MessageException e) { + // fail fast if it could connect but there was a application-level error + throw e; } catch (IllegalStateException e) { - if (e.getCause() instanceof HttpException) { - // fail fast if it could connect but there was a application-level error - throw e; - } switchToOffline(); throw new NotAvailableException(e); } catch (Exception e) { 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 209dd29712f..524a9a034f5 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 @@ -20,10 +20,12 @@ package org.sonar.batch.report; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; import com.google.common.io.Files; import com.squareup.okhttp.HttpUrl; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.Writer; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -42,11 +44,13 @@ import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.text.JsonWriter; import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.protocol.output.BatchReportWriter; import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.WsCe; -import org.sonarqube.ws.client.WsClient; -import org.sonarqube.ws.client.ce.SubmitWsRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsResponse; import static org.apache.commons.lang.StringUtils.defaultIfBlank; @@ -60,7 +64,7 @@ public class ReportPublisher implements Startable { public static final String METADATA_DUMP_FILENAME = "analysis-details.json"; private final Settings settings; - private final WsClient wsClient; + private final BatchWsClient wsClient; private final AnalysisContextReportPublisher contextPublisher; private final ImmutableProjectReactor projectReactor; private final DefaultAnalysisMode analysisMode; @@ -70,7 +74,7 @@ public class ReportPublisher implements Startable { private File reportDir; private BatchReportWriter writer; - public ReportPublisher(Settings settings, WsClient wsClient, AnalysisContextReportPublisher contextPublisher, + public ReportPublisher(Settings settings, BatchWsClient wsClient, AnalysisContextReportPublisher contextPublisher, ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) { this.settings = settings; this.wsClient = wsClient; @@ -145,15 +149,22 @@ public class ReportPublisher implements Startable { LOG.debug("Upload report"); long startTime = System.currentTimeMillis(); ProjectDefinition projectDefinition = projectReactor.getRoot(); - SubmitWsRequest submitRequest = new SubmitWsRequest(); - submitRequest.setProjectKey(projectDefinition.getKey()); - submitRequest.setProjectName(projectDefinition.getName()); - submitRequest.setProjectBranch(projectDefinition.getBranch()); - submitRequest.setReport(report); - WsCe.SubmitResponse submitResponse = wsClient.computeEngine().submit(submitRequest); - long stopTime = System.currentTimeMillis(); - LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms"); - return submitResponse.getTaskId(); + PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report); + PostRequest post = new PostRequest("api/ce/submit") + .setMediaType(MediaTypes.PROTOBUF) + .setParam("projectKey", projectDefinition.getKey()) + .setParam("projectName", projectDefinition.getName()) + .setParam("projectBranch", projectDefinition.getBranch()) + .setPart("report", filePart); + WsResponse response = wsClient.call(post).failIfNotSuccessful(); + try (InputStream protobuf = response.contentStream()) { + return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId(); + } catch (Exception e) { + throw Throwables.propagate(e); + } finally { + long stopTime = System.currentTimeMillis(); + LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms"); + } } @VisibleForTesting @@ -165,13 +176,13 @@ public class ReportPublisher implements Startable { String effectiveKey = projectReactor.getRoot().getKeyWithBranch(); metadata.put("projectKey", effectiveKey); - URL dashboardUrl = HttpUrl.parse(publicUrl()).newBuilder() + URL dashboardUrl = HttpUrl.parse(wsClient.publicBaseUrl()).newBuilder() .addPathSegment("dashboard").addPathSegment("index").addPathSegment(effectiveKey) .build() .url(); metadata.put("dashboardUrl", dashboardUrl.toExternalForm()); - URL taskUrl = HttpUrl.parse(publicUrl()).newBuilder() + URL taskUrl = HttpUrl.parse(wsClient.publicBaseUrl()).newBuilder() .addPathSegment("api").addPathSegment("ce").addPathSegment("task") .addQueryParameter("id", taskId) .build() @@ -202,12 +213,4 @@ public class ReportPublisher implements Startable { throw new IllegalStateException("Unable to dump " + file, e); } } - - /** - * The public URL is optionally configured on server. If not, then the regular URL is returned. - * See https://jira.sonarsource.com/browse/SONAR-4239 - */ - private String publicUrl() { - return defaultIfBlank(settings.getString(CoreProperties.SERVER_BASE_URL), wsClient.wsConnector().baseUrl()); - } } 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 3a00c304aaa..8168c676b8a 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 @@ -36,6 +36,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang.mutable.MutableBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.api.utils.MessageException; import org.sonar.batch.cache.WSLoader; import org.sonar.batch.cache.WSLoaderResult; import org.sonar.batch.util.BatchUtils; @@ -88,6 +89,9 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad HttpException http = (HttpException) t; return http.code() != HttpURLConnection.HTTP_NOT_FOUND; } + if (t instanceof MessageException) { + return true; + } } return false; diff --git a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java index 5687ebdc27c..b175faa6bc6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java @@ -19,39 +19,27 @@ */ package org.sonar.batch.analysis; -import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.cache.WSLoader; import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.home.cache.PersistentCache; -import org.sonarqube.ws.client.WsClient; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; public class AnalysisWSLoaderProviderTest { - @Mock - private PersistentCache cache; - @Mock - private WsClient client; + PersistentCache cache = mock(PersistentCache.class); + BatchWsClient wsClient = mock(BatchWsClient.class); + AnalysisMode mode = mock(AnalysisMode.class); - @Mock - private AnalysisMode mode; - - private AnalysisWSLoaderProvider loaderProvider; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - loaderProvider = new AnalysisWSLoaderProvider(); - } + AnalysisWSLoaderProvider underTest = new AnalysisWSLoaderProvider(); @Test public void testDefault() { - WSLoader loader = loaderProvider.provide(mode, cache, client); + WSLoader loader = underTest.provide(mode, cache, wsClient); assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY); } } 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 fdc159569a0..0acdb08e87c 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 @@ -29,7 +29,6 @@ import org.sonar.batch.cache.WSLoader; import org.sonar.batch.cache.WSLoaderResult; import org.sonar.core.platform.RemotePlugin; import org.sonar.home.cache.FileCache; -import org.sonarqube.ws.client.WsClient; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; @@ -47,7 +46,7 @@ public class BatchPluginInstallerTest { public ExpectedException thrown = ExpectedException.none(); FileCache fileCache = mock(FileCache.class); - WsClient wsClient = mock(WsClient.class); + BatchWsClient wsClient = mock(BatchWsClient.class); BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class); @Test @@ -55,9 +54,9 @@ public class BatchPluginInstallerTest { WSLoader wsLoader = mock(WSLoader.class); when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn(new WSLoaderResult<>("checkstyle\nsqale", true)); - BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); + BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); - List remotePlugins = installer.listRemotePlugins(); + List remotePlugins = underTest.listRemotePlugins(); assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); } @@ -67,10 +66,10 @@ public class BatchPluginInstallerTest { when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); WSLoader wsLoader = mock(WSLoader.class); - BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); + BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); RemotePlugin remote = new RemotePlugin("checkstyle").setFile("checkstyle-plugin.jar", "fakemd5_1"); - File file = installer.download(remote); + File file = underTest.download(remote); assertThat(file).isEqualTo(pluginJar); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java similarity index 80% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java rename to sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java index ac5a9c86683..3b19135fde0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java @@ -24,33 +24,29 @@ import java.util.Map; import org.junit.Test; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonarqube.ws.client.HttpConnector; -import org.sonarqube.ws.client.WsClient; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.batch.bootstrap.WsClientProvider.CONNECT_TIMEOUT_MS; -import static org.sonar.batch.bootstrap.WsClientProvider.DEFAULT_READ_TIMEOUT_SEC; -public class WsClientProviderTest { +public class BatchWsClientProviderTest { - WsClientProvider underTest = new WsClientProvider(); + BatchWsClientProvider underTest = new BatchWsClientProvider(); EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3"); @Test public void provide_client_with_default_settings() { GlobalProperties settings = new GlobalProperties(new HashMap()); - WsClient client = underTest.provide(settings, env); + BatchWsClient client = underTest.provide(settings, env); assertThat(client).isNotNull(); + assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/"); + assertThat(client.publicBaseUrl()).isEqualTo("http://localhost:9000/"); HttpConnector httpConnector = (HttpConnector) client.wsConnector(); assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/"); assertThat(httpConnector.okHttpClient().getProxy()).isNull(); assertThat(httpConnector.okHttpClient().getConnectTimeout()).isEqualTo(5_000); assertThat(httpConnector.okHttpClient().getReadTimeout()).isEqualTo(60_000); assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3"); - assertThat(httpConnector.okHttpClient().interceptors()) - .hasSize(1) - .hasOnlyElementsOfType(WsClientLoggingInterceptor.class); } @Test @@ -62,7 +58,7 @@ public class WsClientProviderTest { props.put("sonar.ws.timeout", "42"); GlobalProperties settings = new GlobalProperties(props); - WsClient client = underTest.provide(settings, env); + BatchWsClient client = underTest.provide(settings, env); assertThat(client).isNotNull(); HttpConnector httpConnector = (HttpConnector) client.wsConnector(); @@ -74,8 +70,8 @@ public class WsClientProviderTest { @Test public void build_singleton() { GlobalProperties settings = new GlobalProperties(new HashMap()); - WsClient first = underTest.provide(settings, env); - WsClient second = underTest.provide(settings, env); + BatchWsClient first = underTest.provide(settings, env); + BatchWsClient second = underTest.provide(settings, env); assertThat(first).isSameAs(second); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java new file mode 100644 index 00000000000..f7b9a216b7b --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java @@ -0,0 +1,142 @@ +/* + * 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 java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.MockWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsRequest; +import org.sonarqube.ws.client.WsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchWsClientTest { + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); + + @Test + public void define_public_url() { + when(wsClient.wsConnector().baseUrl()).thenReturn("https://local/"); + BatchWsClient underTest = new BatchWsClient(wsClient, true, "https://public/"); + assertThat(underTest.baseUrl()).isEqualTo("https://local/"); + assertThat(underTest.publicBaseUrl()).isEqualTo("https://public/"); + } + + /** + * Returned URL has trailing slash, even if configured URL doesn't have. + * That's useful for {@link com.squareup.okhttp.HttpUrl} + */ + @Test + public void public_url_has_trailing_slash() { + BatchWsClient underTest = new BatchWsClient(wsClient, true, "https://public"); + assertThat(underTest.publicBaseUrl()).isEqualTo("https://public/"); + } + + + @Test + public void public_url_is_the_base_url_by_default() { + when(wsClient.wsConnector().baseUrl()).thenReturn("https://local/"); + BatchWsClient underTest = new BatchWsClient(wsClient, true, null); + assertThat(underTest.publicBaseUrl()).isEqualTo("https://local/"); + } + + @Test + public void log_and_profile_request_if_debug_level() throws Exception { + WsRequest request = newRequest(); + WsResponse response = newResponse().setRequestUrl("https://local/api/issues/search"); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + logTester.setLevel(LoggerLevel.DEBUG); + BatchWsClient underTest = new BatchWsClient(wsClient, false, null); + + WsResponse result = underTest.call(request); + + // do not fail the execution -> interceptor returns the response + assertThat(result).isSameAs(response); + + // check logs + List debugLogs = logTester.logs(LoggerLevel.DEBUG); + assertThat(debugLogs).hasSize(1); + assertThat(debugLogs.get(0)).contains("GET 200 https://local/api/issues/search | time="); + } + + @Test + public void fail_if_requires_credentials() throws Exception { + expectedException.expect(MessageException.class); + expectedException + .expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password."); + + WsRequest request = newRequest(); + WsResponse response = newResponse().setCode(401); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, false, null).call(request); + } + + @Test + public void fail_if_credentials_are_not_valid() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password."); + + WsRequest request = newRequest(); + WsResponse response = newResponse().setCode(401); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, /* credentials are configured */true, null).call(request); + } + + @Test + public void fail_if_requires_permission() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("missing scan permission, missing another permission"); + + WsRequest request = newRequest(); + WsResponse response = newResponse() + .setCode(403) + .setContent("{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}"); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, true, null).call(request); + } + + private MockWsResponse newResponse() { + return new MockWsResponse().setRequestUrl("https://local/api/issues/search"); + } + + private WsRequest newRequest() { + return new GetRequest("api/issues/search"); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java deleted file mode 100644 index 4655371e88e..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 com.squareup.okhttp.Interceptor; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.Protocol; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; -import com.squareup.okhttp.ResponseBody; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.MessageException; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class WsClientLoggingInterceptorTest { - - @Rule - public LogTester logTester = new LogTester(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - WsClientLoggingInterceptor underTest = new WsClientLoggingInterceptor(); - Interceptor.Chain chain = mock(Interceptor.Chain.class); - - @Test - public void log_and_profile_request_if_debug_level() throws Exception { - Request request = newRequest(); - Response response = newResponse(request, 200, ""); - when(chain.request()).thenReturn(request); - when(chain.proceed(request)).thenReturn(response); - - logTester.setLevel(LoggerLevel.DEBUG); - Response result = underTest.intercept(chain); - - // do not fail the execution -> interceptor returns the response - assertThat(result).isSameAs(response); - - // check logs - List debugLogs = logTester.logs(LoggerLevel.DEBUG); - assertThat(debugLogs).hasSize(1); - assertThat(debugLogs.get(0)).contains("GET 200 https://localhost:9000/api/issues/search | time="); - List traceLogs = logTester.logs(LoggerLevel.TRACE); - assertThat(traceLogs).hasSize(1); - assertThat(traceLogs.get(0)).isEqualTo("GET https://localhost:9000/api/issues/search"); - } - - @Test - public void fail_if_requires_authentication() throws Exception { - expectedException.expect(MessageException.class); - expectedException - .expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password."); - - Request request = newRequest(); - Response response = newResponse(request, 401, ""); - when(chain.request()).thenReturn(request); - when(chain.proceed(request)).thenReturn(response); - - underTest.intercept(chain); - } - - @Test - public void fail_if_credentials_are_not_valid() throws Exception { - expectedException.expect(MessageException.class); - expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password."); - - Request request = new Request.Builder() - .url("https://localhost:9000/api/issues/search") - .header("Authorization", "Basic BAD_CREDENTIALS") - .get() - .build(); - Response response = newResponse(request, 401, ""); - when(chain.request()).thenReturn(request); - when(chain.proceed(request)).thenReturn(response); - - underTest.intercept(chain); - } - - @Test - public void fail_if_requires_permission() throws Exception { - expectedException.expect(MessageException.class); - expectedException.expectMessage("missing scan permission, missing another permission"); - - Request request = newRequest(); - Response response = newResponse(request, 403, "{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}"); - when(chain.request()).thenReturn(request); - when(chain.proceed(request)).thenReturn(response); - - underTest.intercept(chain); - } - - private Request newRequest() { - return new Request.Builder().url("https://localhost:9000/api/issues/search").get().build(); - } - - private Response newResponse(Request getRequest, int code, String jsonBody) { - return new Response.Builder().request(getRequest) - .code(code) - .protocol(Protocol.HTTP_1_1) - .body(ResponseBody.create(MediaType.parse("application/json"), jsonBody)) - .build(); - } - -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java index 4134b7f6653..0b36dfd3997 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java @@ -23,9 +23,9 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.home.cache.PersistentCache; -import org.sonarqube.ws.client.WsClient; import static org.assertj.core.api.Assertions.assertThat; @@ -34,7 +34,7 @@ public class StrategyWSLoaderProviderTest { private PersistentCache cache; @Mock - private WsClient client; + private BatchWsClient client; @Before public void setUp() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java index 2db585445f8..8b553e8eb02 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java @@ -28,11 +28,11 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.InOrder; import org.mockito.Mockito; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.home.cache.PersistentCache; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.MockWsResponse; -import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsConnector; import org.sonarqube.ws.client.WsRequest; @@ -55,7 +55,7 @@ public class WSLoaderTest { @Rule public ExpectedException exception = ExpectedException.none(); - WsClient ws = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); + BatchWsClient ws = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS); PersistentCache cache = mock(PersistentCache.class); @Test @@ -88,7 +88,7 @@ public class WSLoaderTest { public void put_stream_in_cache() throws IOException { InputStream input = IOUtils.toInputStream("is"); - when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input)); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input)); when(cache.getStream(ID)).thenReturn(input); // SERVER_FIRST -> load from server then put to cache @@ -96,25 +96,24 @@ public class WSLoaderTest { WSLoaderResult result = underTest.loadStream(ID); assertThat(result.get()).isEqualTo(input); - WsConnector wsConnector = ws.wsConnector(); - InOrder inOrder = inOrder(wsConnector, cache); - inOrder.verify(wsConnector).call(any(WsRequest.class)); + InOrder inOrder = inOrder(ws, cache); + inOrder.verify(ws).call(any(WsRequest.class)); inOrder.verify(cache).put(eq(ID), any(InputStream.class)); inOrder.verify(cache).getStream(ID); - verifyNoMoreInteractions(cache, wsConnector); + verifyNoMoreInteractions(cache, ws); } @Test public void test_cache_strategy_fallback() throws IOException { turnCacheEmpty(); - when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws); assertResult(loader.loadString(ID), serverValue, false); - InOrder inOrder = inOrder(ws.wsConnector(), cache); + InOrder inOrder = inOrder(ws, cache); inOrder.verify(cache).getString(ID); - inOrder.verify(ws.wsConnector()).call(any(WsRequest.class)); + inOrder.verify(ws).call(any(WsRequest.class)); } @Test @@ -125,14 +124,14 @@ public class WSLoaderTest { assertResult(loader.loadString(ID), cacheValue, true); - InOrder inOrder = inOrder(ws.wsConnector(), cache); - inOrder.verify(ws.wsConnector()).call(any(WsRequest.class)); + InOrder inOrder = inOrder(ws, cache); + inOrder.verify(ws).call(any(WsRequest.class)); inOrder.verify(cache).getString(ID); } @Test public void test_put_cache() throws IOException { - when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); loader.loadString(ID); verify(cache).put(ID, serverValue.getBytes()); @@ -171,17 +170,14 @@ public class WSLoaderTest { @Test public void test_throw_http_exceptions() { - HttpException httpException = new HttpException("url", 500, "Internal Error"); - IllegalStateException wrapperException = new IllegalStateException(httpException); - - when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(wrapperException); + when(ws.call(any(WsRequest.class))).thenThrow(new HttpException("url", 500)); WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); try { loader.loadString(ID); fail("IllegalStateException expected"); - } catch (IllegalStateException e) { + } catch (HttpException e) { // cache should not be used verifyNoMoreInteractions(cache); } @@ -223,7 +219,7 @@ public class WSLoaderTest { @Test public void test_server_strategy() throws IOException { - when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); assertResult(loader.loadString(ID), serverValue, false); @@ -241,7 +237,7 @@ public class WSLoaderTest { @Test public void test_string() { - when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); assertResult(loader.loadString(ID), serverValue, false); } @@ -251,7 +247,7 @@ public class WSLoaderTest { } private void assertUsedServer(int times) { - verify(ws.wsConnector(), times(times)).call(any(WsRequest.class)); + verify(ws, times(times)).call(any(WsRequest.class)); } private void assertResult(WSLoaderResult result, String expected, boolean fromCache) { @@ -261,7 +257,7 @@ public class WSLoaderTest { } private void turnServerOffline() { - when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(new IllegalStateException()); + when(ws.call(any(WsRequest.class))).thenThrow(new IllegalStateException()); } private void turnCacheEmpty() throws IOException { 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 d1c5e1a93c6..dce14a13b8e 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 @@ -35,9 +35,9 @@ import org.sonar.api.utils.TempFolder; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; import org.sonar.batch.scan.ImmutableProjectReactor; import org.sonar.test.JsonAssert; -import org.sonarqube.ws.client.WsClient; import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +54,7 @@ public class ReportPublisherTest { DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class); Settings settings = new Settings(); - WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); + BatchWsClient wsClient = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS); ImmutableProjectReactor reactor = mock(ImmutableProjectReactor.class); ProjectDefinition root; AnalysisContextReportPublisher contextPublisher = mock(AnalysisContextReportPublisher.class); @@ -63,7 +63,8 @@ public class ReportPublisherTest { public void setUp() { root = ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot()); when(reactor.getRoot()).thenReturn(root); - when(wsClient.wsConnector().baseUrl()).thenReturn("https://localhost"); + when(wsClient.baseUrl()).thenReturn("https://localhost/"); + when(wsClient.publicBaseUrl()).thenReturn("https://public/"); } @Test @@ -73,41 +74,20 @@ public class ReportPublisherTest { underTest.logSuccess("TASK-123"); assertThat(logTester.logs(LoggerLevel.INFO)) - .contains("ANALYSIS SUCCESSFUL, you can browse https://localhost/dashboard/index/struts") + .contains("ANALYSIS SUCCESSFUL, you can browse https://public/dashboard/index/struts") .contains("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report") - .contains("More about the report processing at https://localhost/api/ce/task?id=TASK-123"); + .contains("More about the report processing at https://public/api/ce/task?id=TASK-123"); File detailsFile = new File(temp.getRoot(), "analysis-details.json"); JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" + "\"projectKey\": \"struts\"," + - "\"dashboardUrl\": \"https://localhost/dashboard/index/struts\"," + + "\"dashboardUrl\": \"https://public/dashboard/index/struts\"," + "\"ceTaskId\": \"TASK-123\"," + - "\"ceTaskUrl\": \"https://localhost/api/ce/task?id=TASK-123\"" + + "\"ceTaskUrl\": \"https://public/api/ce/task?id=TASK-123\"" + "}" ); } - @Test - public void log_public_url_if_defined() throws IOException { - settings.setProperty(CoreProperties.SERVER_BASE_URL, "https://publicserver/sonarqube"); - ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); - - underTest.logSuccess("TASK-123"); - - assertThat(logTester.logs(LoggerLevel.INFO)) - .contains("ANALYSIS SUCCESSFUL, you can browse https://publicserver/sonarqube/dashboard/index/struts") - .contains("More about the report processing at https://publicserver/sonarqube/api/ce/task?id=TASK-123"); - - File detailsFile = new File(temp.getRoot(), "analysis-details.json"); - JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" + - "\"projectKey\": \"struts\"," + - "\"dashboardUrl\": \"https://publicserver/sonarqube/dashboard/index/struts\"," + - "\"ceTaskId\": \"TASK-123\"," + - "\"ceTaskUrl\": \"https://publicserver/sonarqube/api/ce/task?id=TASK-123\"" + - "}" - ); - } - @Test public void log_but_not_dump_information_when_report_is_not_uploaded() { ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); 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 e0eebd486d3..5d59cf28121 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 @@ -75,7 +75,7 @@ public class DefaultProjectRepositoriesLoaderTest { @Test(expected = IllegalStateException.class) public void failFastHttpError() { - HttpException http = new HttpException("url", 403, "Forbidden"); + HttpException http = new HttpException("url", 403); IllegalStateException e = new IllegalStateException("http error", http); when(wsLoader.loadStream(anyString())).thenThrow(e); loader.load(PROJECT_KEY, false, null); @@ -86,7 +86,7 @@ public class DefaultProjectRepositoriesLoaderTest { thrown.expect(MessageException.class); thrown.expectMessage("http error"); - HttpException http = new HttpException("uri", 403, "Forbidden"); + HttpException http = new HttpException("uri", 403); MessageException e = MessageException.of("http error", http); when(wsLoader.loadStream(anyString())).thenThrow(e); loader.load(PROJECT_KEY, false, null); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java similarity index 72% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java index 8b9789bcf15..d649f618aa1 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java @@ -17,9 +17,21 @@ * 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.sonarqube.ws.client; -@ParametersAreNonnullByDefault -package org.sonarqube.ws.client.ce; +abstract class BaseResponse implements WsResponse { -import javax.annotation.ParametersAreNonnullByDefault; + @Override + public boolean isSuccessful() { + return code() >= 200 && code() < 300; + } + @Override + public WsResponse failIfNotSuccessful() { + if (!isSuccessful()) { + throw new HttpException(requestUrl(), code()); + } + return this; + } + +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java index 11d4d006419..fea0cecd615 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java @@ -40,20 +40,20 @@ public abstract class BaseService { protected T call(BaseRequest request, Parser parser) { request.setMediaType(MediaTypes.PROTOBUF); - WsResponse response = wsConnector.call(request); + WsResponse response = call(request); return convert(response, parser); } protected WsResponse call(WsRequest request) { - return wsConnector.call(request); + return wsConnector.call(request).failIfNotSuccessful(); } public T convert(WsResponse response, Parser parser) { - try (InputStream byteStream = response.getContentStream()) { + try (InputStream byteStream = response.contentStream()) { // HTTP header "Content-Type" is not verified. It may be different than protobuf. return parser.parseFrom(byteStream); } catch (Exception e) { - throw new IllegalStateException("Fail to parse protobuf response of " + response.getRequestUrl(), e); + throw new IllegalStateException("Fail to parse protobuf response of " + response.requestUrl(), e); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java index 7f46d08cacb..c76672924c5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java @@ -23,7 +23,6 @@ import com.squareup.okhttp.Call; import com.squareup.okhttp.Credentials; import com.squareup.okhttp.Headers; import com.squareup.okhttp.HttpUrl; -import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.MultipartBuilder; import com.squareup.okhttp.OkHttpClient; @@ -32,8 +31,6 @@ import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; import java.net.Proxy; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.CheckForNull; @@ -84,7 +81,6 @@ public class HttpConnector implements WsConnector { this.okHttpClient.setConnectTimeout(builder.connectTimeoutMs, TimeUnit.MILLISECONDS); this.okHttpClient.setReadTimeout(builder.readTimeoutMs, TimeUnit.MILLISECONDS); - this.okHttpClient.interceptors().addAll(builder.interceptors); } @Override @@ -92,18 +88,13 @@ public class HttpConnector implements WsConnector { return baseUrl.url().toExternalForm(); } - public OkHttpClient okHttpClient() { - return okHttpClient; - } - @CheckForNull public String userAgent() { return userAgent; } - @CheckForNull - public String credentials() { - return credentials; + public OkHttpClient okHttpClient() { + return okHttpClient; } @Override @@ -176,9 +167,6 @@ public class HttpConnector implements WsConnector { Call call = okHttpClient.newCall(okRequest); try { Response okResponse = call.execute(); - if (!okResponse.isSuccessful()) { - throw new HttpException(okRequest.urlString(), okResponse.code(), okResponse.message()); - } return new HttpResponse(okResponse); } catch (IOException e) { throw new IllegalStateException("Fail to request " + okRequest.urlString(), e); @@ -195,7 +183,6 @@ public class HttpConnector implements WsConnector { private String proxyPassword; private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS; private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS; - private final List interceptors = new ArrayList<>(); /** * Optional User Agent @@ -260,15 +247,6 @@ public class HttpConnector implements WsConnector { return this; } - /** - * Adds a OkHttp interceptor, for example to log request URLs or response errors. - * See https://github.com/square/okhttp/wiki/Interceptors - */ - public Builder interceptor(Interceptor interceptor) { - this.interceptors.add(interceptor); - return this; - } - public HttpConnector build() { checkArgument(!isNullOrEmpty(url), "Server URL is not defined"); return new HttpConnector(this); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java index 393c6232f89..81fd1d69ab8 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java @@ -27,8 +27,8 @@ public class HttpException extends RuntimeException { private final String url; private final int code; - public HttpException(String url, int code, String message) { - super(String.format("Error %d on %s : %s", code, url, message)); + public HttpException(String url, int code) { + super(String.format("Error %d on %s", code, url)); this.url = url; this.code = code; } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java index b2e3f2766e4..0eac10b596c 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java @@ -26,7 +26,7 @@ import java.io.Reader; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -class HttpResponse implements WsResponse { +class HttpResponse extends BaseResponse { private final Response okResponse; @@ -35,7 +35,12 @@ class HttpResponse implements WsResponse { } @Override - public String getRequestUrl() { + public int code() { + return okResponse.code(); + } + + @Override + public String requestUrl() { return okResponse.request().urlString(); } @@ -45,7 +50,7 @@ class HttpResponse implements WsResponse { } @Override - public String getContentType() { + public String contentType() { return okResponse.header("Content-Type"); } @@ -53,7 +58,7 @@ class HttpResponse implements WsResponse { * Get stream of bytes */ @Override - public InputStream getContentStream() { + public InputStream contentStream() { try { return okResponse.body().byteStream(); } catch (IOException e) { @@ -67,7 +72,7 @@ class HttpResponse implements WsResponse { * charset, this will attempt to decode the response body as UTF-8. */ @Override - public Reader getContentReader() { + public Reader contentReader() { try { return okResponse.body().charStream(); } catch (IOException e) { @@ -76,7 +81,7 @@ class HttpResponse implements WsResponse { } @Override - public String getContent() { + public String content() { try { return okResponse.body().string(); } catch (IOException e) { @@ -85,6 +90,6 @@ class HttpResponse implements WsResponse { } private RuntimeException fail(Exception e) { - throw new IllegalStateException("Fail to read response of " + getRequestUrl(), e); + throw new IllegalStateException("Fail to read response of " + requestUrl(), e); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java index e9e69a7e8b7..d172f4bbf88 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java @@ -20,7 +20,6 @@ package org.sonarqube.ws.client; -import org.sonarqube.ws.client.ce.ComputeEngineService; import org.sonarqube.ws.client.component.ComponentsService; import org.sonarqube.ws.client.issue.IssuesService; import org.sonarqube.ws.client.permission.PermissionsService; @@ -34,7 +33,6 @@ import org.sonarqube.ws.client.usertoken.UserTokensService; */ public class HttpWsClient implements WsClient { - private final ComputeEngineService ceWsClient; private final PermissionsService permissionsService; private final ComponentsService componentsService; private final QualityProfilesService qualityProfilesService; @@ -44,7 +42,6 @@ public class HttpWsClient implements WsClient { public HttpWsClient(WsConnector wsConnector) { this.wsConnector = wsConnector; - this.ceWsClient = new ComputeEngineService(wsConnector); this.permissionsService = new PermissionsService(wsConnector); this.componentsService = new ComponentsService(wsConnector); this.qualityProfilesService = new QualityProfilesService(wsConnector); @@ -62,11 +59,6 @@ public class HttpWsClient implements WsClient { return this.permissionsService; } - @Override - public ComputeEngineService computeEngine() { - return ceWsClient; - } - @Override public ComponentsService components() { return componentsService; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java index 00411fb8601..54f1c05c556 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java @@ -25,20 +25,32 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.sonarqube.ws.MediaTypes; import static java.util.Objects.requireNonNull; -public class MockWsResponse implements WsResponse { +public class MockWsResponse extends BaseResponse { + private int code = HttpURLConnection.HTTP_OK; private String requestUrl; private byte[] content; private String contentType; @Override - public String getContentType() { + public int code() { + return code; + } + + public MockWsResponse setCode(int code) { + this.code = code; + return this; + } + + @Override + public String contentType() { requireNonNull(contentType); return contentType; } @@ -77,25 +89,25 @@ public class MockWsResponse implements WsResponse { } @Override - public String getRequestUrl() { + public String requestUrl() { requireNonNull(requestUrl); return requestUrl; } @Override - public InputStream getContentStream() { + public InputStream contentStream() { requireNonNull(content); return new ByteArrayInputStream(content); } @Override - public Reader getContentReader() { + public Reader contentReader() { requireNonNull(content); return new StringReader(new String(content, StandardCharsets.UTF_8)); } @Override - public String getContent() { + public String content() { requireNonNull(content); return new String(content, StandardCharsets.UTF_8); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java index 4b125627b5b..3f980d7c591 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java @@ -19,7 +19,6 @@ */ package org.sonarqube.ws.client; -import org.sonarqube.ws.client.ce.ComputeEngineService; import org.sonarqube.ws.client.component.ComponentsService; import org.sonarqube.ws.client.issue.IssuesService; import org.sonarqube.ws.client.permission.PermissionsService; @@ -32,8 +31,6 @@ import org.sonarqube.ws.client.usertoken.UserTokensService; public interface WsClient { ComponentsService components(); - ComputeEngineService computeEngine(); - IssuesService issues(); PermissionsService permissions(); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java index d7754d598fc..4d4829b6006 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java @@ -25,18 +25,17 @@ package org.sonarqube.ws.client; */ public interface WsConnector { + /** + * Server base URL, always with trailing slash, for instance "http://localhost:9000/" + */ + String baseUrl(); + /** * @throws IllegalStateException if the request could not be executed due to * a connectivity problem or timeout. Because networks can * fail during an exchange, it is possible that the remote server * accepted the request before the failure - * @throws HttpException if the response code is not in range [200..300) */ WsResponse call(WsRequest wsRequest); - /** - * Server base URL, always with trailing slash, for instance "http://localhost:9000/" - */ - String baseUrl(); - } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java index c6550ffb44a..d1045ba3d4e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java @@ -27,15 +27,35 @@ import java.io.Reader; */ public interface WsResponse { - boolean hasContent(); + /** + * The absolute requested URL + */ + String requestUrl(); + + /** + * HTTP status code + */ + int code(); + + /** + * Returns true if the code is in [200..300), which means the request was + * successfully received, understood, and accepted. + */ + boolean isSuccessful() ; - String getContentType(); + /** + * Throws a {@link HttpException} if {@link #isSuccessful()} is false. + */ + WsResponse failIfNotSuccessful(); + + String contentType(); + + boolean hasContent(); - String getRequestUrl(); + InputStream contentStream(); - InputStream getContentStream(); + Reader contentReader(); - Reader getContentReader(); + String content(); - String getContent(); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java deleted file mode 100644 index 465b96dc7b9..00000000000 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.sonarqube.ws.client.ce; - -import org.sonarqube.ws.MediaTypes; -import org.sonarqube.ws.WsCe; -import org.sonarqube.ws.client.BaseService; -import org.sonarqube.ws.client.PostRequest; -import org.sonarqube.ws.client.WsConnector; - -public class ComputeEngineService extends BaseService { - - public ComputeEngineService(WsConnector wsConnector) { - super(wsConnector, "api/ce"); - } - - public WsCe.SubmitResponse submit(SubmitWsRequest request) { - PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, request.getReport()); - PostRequest post = new PostRequest(path("submit")) - .setParam("projectKey", request.getProjectKey()) - .setParam("projectName", request.getProjectName()) - .setParam("projectBranch", request.getProjectBranch()) - .setPart("report", filePart); - return call(post, WsCe.SubmitResponse.parser()); - } -} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java deleted file mode 100644 index af414ee6fcc..00000000000 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.sonarqube.ws.client.ce; - -import java.io.File; -import javax.annotation.CheckForNull; - -public class SubmitWsRequest { - - private String projectKey; - private String projectName; - private String projectBranch; - private File report; - - public String getProjectKey() { - return projectKey; - } - - public SubmitWsRequest setProjectKey(String projectKey) { - this.projectKey = projectKey; - return this; - } - - @CheckForNull - public String getProjectName() { - return projectName; - } - - public SubmitWsRequest setProjectName(String projectName) { - this.projectName = projectName; - return this; - } - - @CheckForNull - public String getProjectBranch() { - return projectBranch; - } - - public SubmitWsRequest setProjectBranch(String projectBranch) { - this.projectBranch = projectBranch; - return this; - } - - @CheckForNull - public File getReport() { - return report; - } - - public SubmitWsRequest setReport(File report) { - this.report = report; - return this; - } -} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java index 2d92809cbd7..5fbab7b84c9 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java @@ -43,7 +43,7 @@ public class BaseServiceTest { WsResponse response = call(get); - assertThat(response.getContent()).isEqualTo("ok"); + assertThat(response.content()).isEqualTo("ok"); } }.test(); @@ -68,6 +68,25 @@ public class BaseServiceTest { }.test(); } + @Test + public void fail_if_http_error() { + new BaseService(wsConnector, "api/issues") { + + public void test() { + GetRequest get = new GetRequest(path("issue")).setParam("key", "ABC"); + when(wsConnector.call(get)).thenReturn(new MockWsResponse().setCode(403).setRequestUrl("https://local/foo")); + + try { + call(get, Testing.Fake.parser()); + fail(); + } catch (HttpException e) { + assertThat(e.code()).isEqualTo(403); + } + } + + }.test(); + } + @Test public void fail_to_parse_protobuf_response() { new BaseService(wsConnector, "api/issues") { diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java index 79362ea815f..27f68d64bc7 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java @@ -19,14 +19,10 @@ */ package org.sonarqube.ws.client; -import com.squareup.okhttp.Interceptor; -import com.squareup.okhttp.Response; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.RecordedRequest; import java.io.File; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; @@ -70,7 +66,7 @@ public class HttpConnectorTest { // verify response assertThat(response.hasContent()).isTrue(); - assertThat(response.getContent()).isEqualTo("hello, world!"); + assertThat(response.content()).isEqualTo("hello, world!"); // verify the request received by server RecordedRequest recordedRequest = server.takeRequest(); @@ -196,7 +192,7 @@ public class HttpConnectorTest { // verify response assertThat(response.hasContent()).isTrue(); - assertThat(response.getContent()).isEqualTo("hello, world!"); + assertThat(response.content()).isEqualTo("hello, world!"); // verify the request received by server RecordedRequest recordedRequest = server.takeRequest(); @@ -234,34 +230,8 @@ public class HttpConnectorTest { PostRequest request = new PostRequest("api/issues/search"); HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); - try { - underTest.call(request); - fail(); - } catch (HttpException e) { - assertThat(e.code()).isEqualTo(404); - - } - } - - @Test - public void intercept_request_and_response() { - final AtomicBoolean called = new AtomicBoolean(false); - Interceptor interceptor = new Interceptor() { - @Override - public Response intercept(Chain chain) throws IOException { - called.set(true); - return chain.proceed(chain.request()); - } - }; - - answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() - .url(serverUrl) - .interceptor(interceptor) - .build(); - underTest.call(new GetRequest("")); - - assertThat(called.get()).isTrue(); + WsResponse wsResponse = underTest.call(request); + assertThat(wsResponse.code()).isEqualTo(404); } @Test @@ -284,11 +254,11 @@ public class HttpConnectorTest { GetRequest request = new GetRequest("api/issues/search"); answerHelloWorld(); - assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search"); + assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search"); request = new GetRequest("/api/issues/search"); answerHelloWorld(); - assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search"); + assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search"); } private void answerHelloWorld() { diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java index 2db49ea358b..0e8a325e701 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java @@ -26,9 +26,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class HttpExceptionTest { @Test public void test_exception() throws Exception { - HttpException exception = new HttpException("http://localhost:9000/api/search", 500, "Not found"); + HttpException exception = new HttpException("http://localhost:9000/api/search", 500); assertThat(exception.code()).isEqualTo(500); assertThat(exception.url()).isEqualTo("http://localhost:9000/api/search"); - assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search : Not found"); + assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search"); } } -- 2.39.5