diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-04-06 09:57:19 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-04-07 12:12:32 +0200 |
commit | a2be8c1b0c660f10b2e86ac046af3cfaea61214d (patch) | |
tree | a21057b61d9fb834c5c751e6f88021632d7fbcc3 /sonar-ws | |
parent | f6bfd1d2f0373da2d2929d9217721a81fc3afe5d (diff) | |
download | sonarqube-a2be8c1b0c660f10b2e86ac046af3cfaea61214d.tar.gz sonarqube-a2be8c1b0c660f10b2e86ac046af3cfaea61214d.zip |
SONAR-6948 Ability for Java server extensions to call web services
Diffstat (limited to 'sonar-ws')
14 files changed, 528 insertions, 30 deletions
diff --git a/sonar-ws/pom.xml b/sonar-ws/pom.xml index 9b5cc87d130..e6a19ead6ba 100644 --- a/sonar-ws/pom.xml +++ b/sonar-ws/pom.xml @@ -39,6 +39,12 @@ <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> </dependency> + <dependency> + <groupId>org.sonarsource.sonarqube</groupId> + <artifactId>sonar-plugin-api</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> <!-- Tests --> <dependency> diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java index 83c7163e384..2cfcd7448a5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java @@ -19,6 +19,8 @@ */ package org.sonarqube.ws.client; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; + abstract class BaseResponse implements WsResponse { @Override @@ -34,4 +36,8 @@ abstract class BaseResponse implements WsResponse { return this; } + @Override + public boolean hasContent() { + return code() != HTTP_NO_CONTENT; + } } 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 aa5abe9b0a8..8d65ec983bb 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 @@ -26,6 +26,7 @@ import java.io.InputStream; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.apache.commons.io.IOUtils; import org.sonarqube.ws.MediaTypes; import static com.google.common.base.Preconditions.checkArgument; @@ -56,8 +57,9 @@ public abstract class BaseService { public <T extends Message> T convert(WsResponse response, Parser<T> parser) { try (InputStream byteStream = response.contentStream()) { + byte[] bytes = IOUtils.toByteArray(byteStream); // HTTP header "Content-Type" is not verified. It may be different than protobuf. - return parser.parseFrom(byteStream); + return parser.parseFrom(bytes); } catch (Exception 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/HttpWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java index 5ceb0b6f27f..1c0d8278ee9 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java @@ -31,11 +31,12 @@ import org.sonarqube.ws.client.system.SystemService; import org.sonarqube.ws.client.usertoken.UserTokensService; /** - * Entry point of the Java Client for SonarQube Web Services + * This class is not public anymore since version 5.5. It is + * created by {@link WsClientFactory} * * @since 5.3 */ -public class HttpWsClient implements WsClient { +class DefaultWsClient implements WsClient { private final WsConnector wsConnector; private final PermissionsService permissionsService; @@ -49,7 +50,7 @@ public class HttpWsClient implements WsClient { private final CeService ceService; private final RulesService rulesService; - public HttpWsClient(WsConnector wsConnector) { + DefaultWsClient(WsConnector wsConnector) { this.wsConnector = wsConnector; this.permissionsService = new PermissionsService(wsConnector); this.componentsService = new ComponentsService(wsConnector); 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 4c0917f751c..f66bcd6330c 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 @@ -202,16 +202,23 @@ public class HttpConnector implements WsConnector { return okHttpRequestBuilder; } - private HttpResponse doCall(Request okRequest) { + private OkHttpResponse doCall(Request okRequest) { Call call = okHttpClient.newCall(okRequest); try { Response okResponse = call.execute(); - return new HttpResponse(okResponse); + return new OkHttpResponse(okResponse); } catch (IOException e) { throw new IllegalStateException("Fail to request " + okRequest.urlString(), e); } } + /** + * @since 5.5 + */ + public static Builder newBuilder() { + return new Builder(); + } + public static class Builder { private String url; private String userAgent; @@ -224,6 +231,13 @@ public class HttpConnector implements WsConnector { private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS; /** + * Private since 5.5. + * @see HttpConnector#newBuilder() + */ + private Builder() { + } + + /** * Optional User Agent */ public Builder userAgent(@Nullable String userAgent) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsClientFactory.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsClientFactory.java new file mode 100644 index 00000000000..227ea094104 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsClientFactory.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +import org.sonar.api.server.ServerSide; +import org.sonar.api.server.ws.LocalConnector; + +@ServerSide +public interface LocalWsClientFactory extends WsClientFactory { + + WsClient newClient(LocalConnector localConnector); + +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsConnector.java new file mode 100644 index 00000000000..3b7af43aa88 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsConnector.java @@ -0,0 +1,132 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +import com.google.common.annotations.VisibleForTesting; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import org.sonar.api.server.ws.LocalConnector; + +import static java.nio.charset.StandardCharsets.UTF_8; + +class LocalWsConnector implements WsConnector { + + private final LocalConnector localConnector; + + LocalWsConnector(LocalConnector localConnector) { + this.localConnector = localConnector; + } + + @VisibleForTesting + LocalConnector getLocalConnector() { + return localConnector; + } + + @Override + public String baseUrl() { + return "/"; + } + + @Override + public WsResponse call(WsRequest wsRequest) { + DefaultLocalRequest localRequest = new DefaultLocalRequest(wsRequest); + LocalConnector.LocalResponse localResponse = localConnector.call(localRequest); + return new ByteArrayResponse(wsRequest.getPath(), localResponse); + } + + private static class DefaultLocalRequest implements LocalConnector.LocalRequest { + private final WsRequest wsRequest; + + public DefaultLocalRequest(WsRequest wsRequest) { + this.wsRequest = wsRequest; + } + + @Override + public String getPath() { + return wsRequest.getPath(); + } + + @Override + public String getMediaType() { + return wsRequest.getMediaType(); + } + + @Override + public String getMethod() { + return wsRequest.getMethod().name(); + } + + @Override + public boolean hasParam(String key) { + return wsRequest.getParams().containsKey(key); + } + + @Override + public String getParam(String key) { + return wsRequest.getParams().get(key); + } + } + + private static class ByteArrayResponse extends BaseResponse { + private final String path; + private final byte[] bytes; + private final String contentType; + private final int code; + + ByteArrayResponse(String path, LocalConnector.LocalResponse localResponse) { + this.path = path; + this.bytes = localResponse.getBytes(); + this.contentType = localResponse.getMediaType(); + this.code = localResponse.getStatus(); + } + + @Override + public String requestUrl() { + return path; + } + + @Override + public int code() { + return code; + } + + @Override + public String contentType() { + return contentType; + } + + @Override + public InputStream contentStream() { + return new ByteArrayInputStream(bytes); + } + + @Override + public Reader contentReader() { + return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8); + } + + @Override + public String content() { + return new String(bytes, UTF_8); + } + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpResponse.java index b770424728a..30217f16681 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpResponse.java @@ -24,13 +24,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; - -class HttpResponse extends BaseResponse { +class OkHttpResponse extends BaseResponse { private final Response okResponse; - HttpResponse(Response okResponse) { + OkHttpResponse(Response okResponse) { this.okResponse = okResponse; } @@ -45,11 +43,6 @@ class HttpResponse extends BaseResponse { } @Override - public boolean hasContent() { - return okResponse.code() != HTTP_NO_CONTENT; - } - - @Override public String contentType() { return okResponse.header("Content-Type"); } 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 522e6054a46..fc79d58e695 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 @@ -31,6 +31,21 @@ import org.sonarqube.ws.client.system.SystemService; import org.sonarqube.ws.client.usertoken.UserTokensService; /** + * Allows to request the web services of SonarQube server. Instance is provided by + * {@link WsClientFactory}. + * + * <p> + * Usage: + * <pre> + * HttpConnector httpConnector = HttpConnector.newBuilder() + * .url("http://localhost:9000") + * .credentials("admin", "admin") + * .build(); + * WsClient wsClient = WsClientFactories.getDefault().newClient(httpConnector); + * wsClient.issues().search(issueRequest); + * </pre> + * </p> + * * @since 5.3 */ public interface WsClient { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactories.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactories.java new file mode 100644 index 00000000000..1f0898f95e3 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactories.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +import org.sonar.api.server.ws.LocalConnector; + +/** + * All provided implementations of {@link WsClientFactory}. + */ +public class WsClientFactories { + + private WsClientFactories() { + // prevent instantiation + } + + /** + * Factory to be used when connecting to a remote SonarQube web server. + */ + public static WsClientFactory getDefault() { + return DefaultWsClientFactory.INSTANCE; + } + + /** + * Factory that allows a SonarQube web service to interact + * with other web services, without using the HTTP stack. + * @see org.sonar.api.server.ws.LocalConnector + */ + public static LocalWsClientFactory getLocal() { + return DefaultLocalWsClientFactory.INSTANCE; + } + + private enum DefaultWsClientFactory implements WsClientFactory { + INSTANCE; + + @Override + public WsClient newClient(WsConnector connector) { + return new DefaultWsClient(connector); + } + } + + private enum DefaultLocalWsClientFactory implements LocalWsClientFactory { + INSTANCE; + + @Override + public WsClient newClient(WsConnector connector) { + return DefaultWsClientFactory.INSTANCE.newClient(connector); + } + + @Override + public WsClient newClient(LocalConnector localConnector) { + return new DefaultWsClient(new LocalWsConnector(localConnector)); + } + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactory.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactory.java new file mode 100644 index 00000000000..d7a89c2e742 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactory.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +/** + * Creates {@link WsClient}. Implementations are provided by {@link WsClientFactories}. + * + * @since 5.5 + */ +public interface WsClientFactory { + + WsClient newClient(WsConnector connector); + +} 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 a18407bd28b..767978108a7 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 @@ -60,7 +60,7 @@ public class HttpConnectorTest { @Test public void test_default_settings() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); assertThat(underTest.baseUrl()).isEqualTo(serverUrl); GetRequest request = new GetRequest("api/issues/search").setMediaType(MediaTypes.PROTOBUF); WsResponse response = underTest.call(request); @@ -87,7 +87,7 @@ public class HttpConnectorTest { @Test public void use_basic_authentication() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .credentials("theLogin", "thePassword") .build(); @@ -102,7 +102,7 @@ public class HttpConnectorTest { @Test public void use_basic_authentication_with_null_password() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .credentials("theLogin", null) .build(); @@ -121,7 +121,7 @@ public class HttpConnectorTest { @Test public void use_access_token() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .token("theToken") .build(); @@ -136,7 +136,7 @@ public class HttpConnectorTest { @Test public void use_proxy_authentication() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .proxyCredentials("theProxyLogin", "theProxyPassword") .build(); @@ -150,7 +150,7 @@ public class HttpConnectorTest { @Test public void override_timeouts() { - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .readTimeoutMilliseconds(42) .connectTimeoutMilliseconds(74) @@ -163,7 +163,7 @@ public class HttpConnectorTest { @Test public void send_user_agent() throws Exception { answerHelloWorld(); - HttpConnector underTest = new HttpConnector.Builder() + HttpConnector underTest = HttpConnector.newBuilder() .url(serverUrl) .userAgent("Maven Plugin/2.3") .build(); @@ -176,7 +176,7 @@ public class HttpConnectorTest { @Test public void fail_if_unknown_implementation_of_request() { - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); try { underTest.call(mock(WsRequest.class)); fail(); @@ -192,7 +192,7 @@ public class HttpConnectorTest { .setParam("severity", "MAJOR") .setMediaType(MediaTypes.PROTOBUF); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); WsResponse response = underTest.call(request); // verify response @@ -215,7 +215,7 @@ public class HttpConnectorTest { .setPart("report", new PostRequest.Part(MediaTypes.TXT, reportFile)) .setMediaType(MediaTypes.PROTOBUF); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); WsResponse response = underTest.call(request); assertThat(response.hasContent()).isTrue(); @@ -233,7 +233,7 @@ public class HttpConnectorTest { public void http_error() throws Exception { server.enqueue(new MockResponse().setResponseCode(404)); PostRequest request = new PostRequest("api/issues/search"); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); WsResponse wsResponse = underTest.call(request); assertThat(wsResponse.code()).isEqualTo(404); @@ -242,7 +242,7 @@ public class HttpConnectorTest { @Test public void support_base_url_ending_with_slash() throws Exception { assertThat(serverUrl).endsWith("/"); - HttpConnector underTest = new HttpConnector.Builder().url(StringUtils.removeEnd(serverUrl, "/")).build(); + HttpConnector underTest = HttpConnector.newBuilder().url(StringUtils.removeEnd(serverUrl, "/")).build(); GetRequest request = new GetRequest("api/issues/search"); answerHelloWorld(); @@ -255,7 +255,7 @@ public class HttpConnectorTest { public void support_base_url_with_context() { // just to be sure assertThat(serverUrl).endsWith("/"); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl + "sonar").build(); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl + "sonar").build(); GetRequest request = new GetRequest("api/issues/search"); answerHelloWorld(); @@ -269,7 +269,7 @@ public class HttpConnectorTest { @Test public void support_tls_1_2_on_java7() { when(javaVersion.isJava7()).thenReturn(true); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion); assertTlsAndClearTextSpecifications(underTest); // enable TLS 1.0, 1.1 and 1.2 @@ -279,7 +279,7 @@ public class HttpConnectorTest { @Test public void support_tls_versions_of_java8() { when(javaVersion.isJava7()).thenReturn(false); - HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion); + HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion); assertTlsAndClearTextSpecifications(underTest); assertThat(underTest.okHttpClient().getSslSocketFactory()).isInstanceOf(SSLSocketFactory.getDefault().getClass()); diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/LocalWsConnectorTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/LocalWsConnectorTest.java new file mode 100644 index 00000000000..d92f4a8d3d5 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/LocalWsConnectorTest.java @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +import com.google.common.collect.ImmutableMap; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Test; +import org.sonar.api.server.ws.LocalConnector; +import org.sonarqube.ws.MediaTypes; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class LocalWsConnectorTest { + + LocalConnector connector = mock(LocalConnector.class); + LocalWsConnector underTest = new LocalWsConnector(connector); + + @Test + public void baseUrl_is_always_slash() { + assertThat(underTest.baseUrl()).isEqualTo("/"); + } + + @Test + public void call_request() throws Exception { + WsRequest wsRequest = new PostRequest("api/issues/search") + .setMediaType(MediaTypes.JSON) + .setParam("foo", "bar"); + answer(new DumbLocalResponse(400, MediaTypes.JSON, "{}".getBytes(UTF_8), Collections.<String>emptyList())); + + WsResponse wsResponse = underTest.call(wsRequest); + + verifyRequested("POST", "api/issues/search", MediaTypes.JSON, ImmutableMap.of("foo", "bar")); + assertThat(wsResponse.code()).isEqualTo(400); + assertThat(wsResponse.content()).isEqualTo("{}"); + assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo("{}"); + assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo("{}"); + assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON); + assertThat(wsResponse.requestUrl()).isEqualTo("api/issues/search"); + } + + @Test + public void call_request_with_defaults() throws Exception { + // no parameters, no media type + WsRequest wsRequest = new GetRequest("api/issues/search"); + answer(new DumbLocalResponse(200, MediaTypes.JSON, "".getBytes(UTF_8), Collections.<String>emptyList())); + + WsResponse wsResponse = underTest.call(wsRequest); + + verifyRequested("GET", "api/issues/search", MediaTypes.JSON, Collections.<String, String>emptyMap()); + assertThat(wsResponse.code()).isEqualTo(200); + assertThat(wsResponse.content()).isEqualTo(""); + assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo(""); + assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo(""); + assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON); + } + + private void answer(DumbLocalResponse response) { + when(connector.call(any(LocalConnector.LocalRequest.class))).thenReturn(response); + } + + private void verifyRequested(final String expectedMethod, final String expectedPath, + final String expectedMediaType, + final Map<String, String> expectedParams) { + verify(connector).call(argThat(new TypeSafeMatcher<LocalConnector.LocalRequest>() { + @Override + protected boolean matchesSafely(LocalConnector.LocalRequest localRequest) { + boolean ok = localRequest.getMethod().equals(expectedMethod) && localRequest.getPath().equals(expectedPath); + ok &= localRequest.getMediaType().equals(expectedMediaType); + for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) { + String paramKey = expectedParam.getKey(); + ok &= localRequest.hasParam(paramKey); + ok &= expectedParam.getValue().equals(localRequest.getParam(paramKey)); + } + return ok; + } + + @Override + public void describeTo(Description description) { + } + })); + } + + private static class DumbLocalResponse implements LocalConnector.LocalResponse { + private final int code; + private final String mediaType; + private final byte[] bytes; + private final List<String> headers; + + public DumbLocalResponse(int code, String mediaType, byte[] bytes, List<String> headers) { + this.code = code; + this.mediaType = mediaType; + this.bytes = bytes; + this.headers = headers; + } + + @Override + public int getStatus() { + return code; + } + + @Override + public String getMediaType() { + return mediaType; + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public Collection<String> getHeaderNames() { + return headers; + } + + @Override + public String getHeader(String name) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientFactoriesTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientFactoriesTest.java new file mode 100644 index 00000000000..351a80dc295 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientFactoriesTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +import org.junit.Test; +import org.sonar.api.server.ws.LocalConnector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class WsClientFactoriesTest { + @Test + public void create_http_client() { + HttpConnector connector = HttpConnector.newBuilder().url("http://localhost:9000").build(); + WsClient client = WsClientFactories.getDefault().newClient(connector); + + assertThat(client).isInstanceOf(DefaultWsClient.class); + assertThat(client.wsConnector()).isSameAs(connector); + } + + @Test + public void create_local_client() { + LocalConnector connector = mock(LocalConnector.class); + WsClient client = WsClientFactories.getLocal().newClient(connector); + + assertThat(client).isInstanceOf(DefaultWsClient.class); + assertThat(client.wsConnector()).isInstanceOf(LocalWsConnector.class); + assertThat(((LocalWsConnector) client.wsConnector()).getLocalConnector()).isSameAs(connector); + } +} |