aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2015-10-12 17:18:25 +0200
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2015-10-14 17:29:26 +0200
commitfce2babd2602ee29558194096ceea3bdbfe8f590 (patch)
tree4d714dad7bdf2efdd41ff9eefd87043f7cc7caf6
parent2050de252f953c38a4ca2c4baa43d3e715bb229f (diff)
downloadsonarqube-fce2babd2602ee29558194096ceea3bdbfe8f590.tar.gz
sonarqube-fce2babd2602ee29558194096ceea3bdbfe8f590.zip
Create new WsClient to request WS
-rw-r--r--sonar-ws/pom.xml58
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java43
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java220
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java168
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java80
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/package-info.java22
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java34
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java138
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java128
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java71
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java141
11 files changed, 1102 insertions, 1 deletions
diff --git a/sonar-ws/pom.xml b/sonar-ws/pom.xml
index c82579b6d41..3b6e21d7cd7 100644
--- a/sonar-ws/pom.xml
+++ b/sonar-ws/pom.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -21,6 +22,61 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+
+ <!-- Tests -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Jetty dependencies -->
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>test-jetty-servlet</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.json-simple</groupId>
+ <artifactId>json-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.kevinsawicki</groupId>
+ <artifactId>http-request</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ <version>3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.3.5</version>
+ </dependency>
</dependencies>
<build>
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
new file mode 100644
index 00000000000..09aa68e5995
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * @since 3.6
+ */
+public class HttpException extends RuntimeException {
+
+ private final String url;
+ private final int status;
+
+ public HttpException(String url, int status, String message) {
+ super(String.format("Error %d on %s : %s", status, url, message));
+ this.url = url;
+ this.status = status;
+ }
+
+ public String url() {
+ return url;
+ }
+
+ public int status() {
+ return status;
+ }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java
new file mode 100644
index 00000000000..4ae2ce1f912
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java
@@ -0,0 +1,220 @@
+/*
+ * 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;
+
+import com.github.kevinsawicki.http.HttpRequest;
+import com.google.common.base.Throwables;
+import com.google.common.net.MediaType;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Message;
+import com.google.protobuf.Parser;
+import java.util.Arrays;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+import static java.net.HttpURLConnection.HTTP_CREATED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_OK;
+
+/**
+ * Not an API. Please do not use this class, except maybe for unit tests.
+ */
+public class HttpRequestFactory {
+
+ private static final int[] RESPONSE_SUCCESS = {HTTP_OK, HTTP_CREATED, HTTP_NO_CONTENT};
+
+ private final String baseUrl;
+ private String login, password, proxyHost, proxyLogin, proxyPassword;
+ private int proxyPort;
+ private int connectTimeoutInMilliseconds;
+ private int readTimeoutInMilliseconds;
+
+ public HttpRequestFactory(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ public HttpRequestFactory setLogin(@Nullable String login) {
+ this.login = login;
+ return this;
+ }
+
+ public HttpRequestFactory setPassword(@Nullable String password) {
+ this.password = password;
+ return this;
+ }
+
+ public HttpRequestFactory setProxyHost(@Nullable String proxyHost) {
+ this.proxyHost = proxyHost;
+ return this;
+ }
+
+ public HttpRequestFactory setProxyLogin(@Nullable String proxyLogin) {
+ this.proxyLogin = proxyLogin;
+ return this;
+ }
+
+ public HttpRequestFactory setProxyPassword(@Nullable String proxyPassword) {
+ this.proxyPassword = proxyPassword;
+ return this;
+ }
+
+ public HttpRequestFactory setProxyPort(int proxyPort) {
+ this.proxyPort = proxyPort;
+ return this;
+ }
+
+ public HttpRequestFactory setConnectTimeoutInMilliseconds(int connectTimeoutInMilliseconds) {
+ this.connectTimeoutInMilliseconds = connectTimeoutInMilliseconds;
+ return this;
+ }
+
+ public HttpRequestFactory setReadTimeoutInMilliseconds(int readTimeoutInMilliseconds) {
+ this.readTimeoutInMilliseconds = readTimeoutInMilliseconds;
+ return this;
+ }
+
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getProxyHost() {
+ return proxyHost;
+ }
+
+ public String getProxyLogin() {
+ return proxyLogin;
+ }
+
+ public String getProxyPassword() {
+ return proxyPassword;
+ }
+
+ public int getProxyPort() {
+ return proxyPort;
+ }
+
+ public int getConnectTimeoutInMilliseconds() {
+ return connectTimeoutInMilliseconds;
+ }
+
+ public int getReadTimeoutInMilliseconds() {
+ return readTimeoutInMilliseconds;
+ }
+
+ public String get(String wsUrl, Map<String, Object> queryParams) {
+ HttpRequest request = prepare(HttpRequest.get(buildUrl(wsUrl), queryParams, true));
+ return execute(request);
+ }
+
+ public String post(String wsUrl, Map<String, Object> queryParams) {
+ HttpRequest request = prepare(HttpRequest.post(buildUrl(wsUrl), true)).form(queryParams, HttpRequest.CHARSET_UTF8);
+ return execute(request);
+ }
+
+ public String execute(WsRequest wsRequest) {
+ HttpRequest httpRequest = wsRequestToHttpRequest(wsRequest);
+ return execute(httpRequest);
+ }
+
+ public <T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser) {
+ HttpRequest httpRequest = wsRequestToHttpRequest(wsRequest);
+ try {
+ return protobufParser.parseFrom(httpRequest.bytes());
+ } catch (InvalidProtocolBufferException e) {
+ Throwables.propagate(e);
+ }
+
+ throw new IllegalStateException("Uncatched exception when parsing protobuf response");
+ }
+
+ private HttpRequest wsRequestToHttpRequest(WsRequest wsRequest) {
+ HttpRequest httpRequest = wsRequest.getMethod().equals(WsRequest.Method.GET)
+ ? HttpRequest.post(buildUrl(wsRequest.getUrl()), wsRequest.getParams(), true)
+ : HttpRequest.get(buildUrl(wsRequest.getUrl()), wsRequest.getParams(), true);
+ httpRequest = prepare(httpRequest);
+ switch (wsRequest.getMediaType()) {
+ case PROTOBUF:
+ httpRequest.accept(MediaType.PROTOBUF.toString());
+ break;
+ default:
+ httpRequest.accept(MediaType.JSON_UTF_8.toString());
+ break;
+ }
+
+ return httpRequest;
+ }
+
+ private String buildUrl(String part) {
+ StringBuilder url = new StringBuilder();
+ url.append(baseUrl);
+ if (!part.startsWith("/")) {
+ url.append('/');
+ }
+ url.append(part);
+ return url.toString();
+ }
+
+ private String execute(HttpRequest request) {
+ try {
+ if (isSuccess(request)) {
+ return request.body(HttpRequest.CHARSET_UTF8);
+ }
+ // TODO better handle error messages
+ throw new HttpException(request.url().toString(), request.code(), request.body());
+
+ } catch (HttpRequest.HttpRequestException e) {
+ throw new IllegalStateException("Fail to request " + request.url(), e.getCause());
+ }
+ }
+
+ private boolean isSuccess(HttpRequest request) {
+ return Arrays.binarySearch(RESPONSE_SUCCESS, request.code()) >= 0;
+ }
+
+ private HttpRequest prepare(HttpRequest request) {
+ if (proxyHost != null) {
+ request.useProxy(proxyHost, proxyPort);
+ if (proxyLogin != null) {
+ request.proxyBasic(proxyLogin, proxyPassword);
+ }
+ }
+ request
+ .acceptGzipEncoding()
+ .uncompress(true)
+ .acceptJson()
+ .acceptCharset(HttpRequest.CHARSET_UTF8)
+ .connectTimeout(connectTimeoutInMilliseconds)
+ .readTimeout(readTimeoutInMilliseconds)
+ .trustAllCerts()
+ .trustAllHosts();
+ if (login != null) {
+ request.basic(login, password);
+ }
+ return request;
+ }
+}
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
new file mode 100644
index 00000000000..e6e6c827bd6
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+import com.google.protobuf.Message;
+import com.google.protobuf.Parser;
+import javax.annotation.Nullable;
+
+/**
+ * Entry point of the Java Client for SonarQube Web Services.
+ * <p/>
+ * Example:
+ * <pre>
+ * WsClient client = WsClient.create("http://localhost:9000");
+ * </pre>
+ *
+ * @since 5.2
+ */
+public class WsClient {
+
+ public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30000;
+ public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60000;
+
+ /**
+ * Visibility relaxed for unit tests
+ */
+ final HttpRequestFactory requestFactory;
+
+ private WsClient(Builder builder) {
+ this(new HttpRequestFactory(builder.url)
+ .setLogin(builder.login)
+ .setPassword(builder.password)
+ .setProxyHost(builder.proxyHost)
+ .setProxyPort(builder.proxyPort)
+ .setProxyLogin(builder.proxyLogin)
+ .setProxyPassword(builder.proxyPassword)
+ .setConnectTimeoutInMilliseconds(builder.connectTimeoutMs)
+ .setReadTimeoutInMilliseconds(builder.readTimeoutMs));
+ }
+
+ /**
+ * Visible for testing
+ */
+ WsClient(HttpRequestFactory requestFactory) {
+ this.requestFactory = requestFactory;
+ }
+
+ /**
+ * Create a builder of {@link WsClient}s.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Create a client with default configuration. Use {@link #builder()} to define
+ * a custom configuration (credentials, HTTP proxy, HTTP timeouts).
+ */
+ public static WsClient create(String serverUrl) {
+ return builder().url(serverUrl).build();
+ }
+
+ public String execute(WsRequest wsRequest) {
+ return requestFactory.execute(wsRequest);
+ }
+
+ public <T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser) {
+ return requestFactory.execute(wsRequest, protobufParser);
+ }
+
+ public static class Builder {
+ private String login, password, url, proxyHost, proxyLogin, proxyPassword;
+ private int proxyPort = 0;
+ private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS, readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS;
+
+ private Builder() {
+ }
+
+ /**
+ * Mandatory HTTP server URL, eg "http://localhost:9000"
+ */
+ public Builder url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Optional login, for example "admin"
+ */
+ public Builder login(@Nullable String login) {
+ this.login = login;
+ return this;
+ }
+
+ /**
+ * Optional password related to {@link #login(String)}, for example "admin"
+ */
+ public Builder password(@Nullable String password) {
+ this.password = password;
+ return this;
+ }
+
+ /**
+ * Host and port of the optional HTTP proxy
+ */
+ public Builder proxy(@Nullable String proxyHost, int proxyPort) {
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ return this;
+ }
+
+ public Builder proxyLogin(@Nullable String proxyLogin) {
+ this.proxyLogin = proxyLogin;
+ return this;
+ }
+
+ public Builder proxyPassword(@Nullable String proxyPassword) {
+ this.proxyPassword = proxyPassword;
+ return this;
+ }
+
+ /**
+ * Sets a specified timeout value, in milliseconds, to be used when opening HTTP connection.
+ * A timeout of zero is interpreted as an infinite timeout. Default value is {@link WsClient#DEFAULT_CONNECT_TIMEOUT_MILLISECONDS}
+ */
+ public Builder connectTimeoutMilliseconds(int i) {
+ this.connectTimeoutMs = i;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout to a specified timeout, in milliseconds.
+ * A timeout of zero is interpreted as an infinite timeout. Default value is {@link WsClient#DEFAULT_READ_TIMEOUT_MILLISECONDS}
+ */
+ public Builder readTimeoutMilliseconds(int i) {
+ this.readTimeoutMs = i;
+ return this;
+ }
+
+ /**
+ * Build a new client
+ */
+ public WsClient build() {
+ if (url == null || "".equals(url)) {
+ throw new IllegalStateException("Server URL must be set");
+ }
+ return new WsClient(this);
+ }
+ }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java
new file mode 100644
index 00000000000..9641e0de785
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class WsRequest {
+ private final Map<String, Object> params = new HashMap<>();
+ private Method method = Method.GET;
+ private MediaType mimeType = MediaType.JSON;
+ private String url;
+
+ public WsRequest(String url) {
+ this.url = url;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public WsRequest setMethod(Method method) {
+ checkNotNull(method);
+ this.method = method;
+ return this;
+ }
+
+ public MediaType getMediaType() {
+ return mimeType;
+ }
+
+ public WsRequest setMediaType(MediaType type) {
+ checkNotNull(type);
+ this.mimeType = type;
+ return this;
+ }
+
+ public WsRequest setParam(String key, Object value) {
+ checkNotNull(key);
+ checkNotNull(value);
+ this.params.put(key, value);
+ return this;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public Map<String, Object> getParams() {
+ return params;
+ }
+
+ public enum Method {
+ GET, POST
+ }
+
+ public enum MediaType {
+ PROTOBUF, JSON, TEXT
+ }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/package-info.java
new file mode 100644
index 00000000000..d9978db6002
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonarqube.ws.client;
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
new file mode 100644
index 00000000000..3eab476fec9
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+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");
+ assertThat(exception.status()).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");
+ }
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java
new file mode 100644
index 00000000000..f14ad893b54
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+
+import java.net.ConnectException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class HttpRequestFactoryTest {
+ @Rule
+ public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
+
+ @Test
+ public void test_get() {
+ httpServer.stubStatusCode(200).stubResponseBody("{'issues': []}");
+
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
+ String json = factory.get("/api/issues", Collections.<String, Object>emptyMap());
+
+ assertThat(json).isEqualTo("{'issues': []}");
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
+ }
+
+ @Test
+ public void should_throw_illegal_state_exc_if_connect_exception() {
+ HttpRequestFactory factory = new HttpRequestFactory("http://localhost:1");
+ try {
+ factory.get("/api/issues", Collections.<String, Object>emptyMap());
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalStateException.class);
+ assertThat(e).hasMessage("Fail to request http://localhost:1/api/issues");
+ assertThat(e.getCause().getMessage()).matches(".*(Connection refused|Connexion refusée).*");
+ }
+ }
+
+ @Test
+ public void test_post() {
+ httpServer.stubStatusCode(200).stubResponseBody("{}");
+
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
+ String json = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
+
+ assertThat(json).isEqualTo("{}");
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change");
+ }
+
+ @Test
+ public void post_successful_if_created() {
+ httpServer.stubStatusCode(201).stubResponseBody("{}");
+
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
+ String json = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
+
+ assertThat(json).isEqualTo("{}");
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change");
+ }
+
+ @Test
+ public void post_successful_if_no_content() {
+ httpServer.stubStatusCode(204).stubResponseBody("");
+
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
+ String json = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
+
+ assertThat(json).isEqualTo("");
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change");
+ }
+
+ @Test
+ public void test_authentication() {
+ httpServer.stubStatusCode(200).stubResponseBody("{}");
+
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url()).setLogin("karadoc").setPassword("legrascestlavie");
+ String json = factory.get("/api/issues", Collections.<String, Object>emptyMap());
+
+ assertThat(json).isEqualTo("{}");
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
+ assertThat(httpServer.requestHeaders().get("Authorization")).isEqualTo("Basic a2FyYWRvYzpsZWdyYXNjZXN0bGF2aWU=");
+ }
+
+ @Test
+ public void test_proxy() throws Exception {
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url())
+ .setProxyHost("localhost").setProxyPort(1)
+ .setProxyLogin("john").setProxyPassword("smith");
+ try {
+ factory.get("/api/issues", Collections.<String, Object>emptyMap());
+ fail();
+ } catch (IllegalStateException e) {
+ // it's not possible to check that the proxy is correctly configured
+ assertThat(e.getCause()).isInstanceOf(ConnectException.class);
+ }
+ }
+
+ @Test
+ public void beginning_slash_is_optional() throws Exception {
+ HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
+ factory.get("api/foo", Collections.<String, Object>emptyMap());
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/foo");
+
+ factory.get("/api/bar", Collections.<String, Object>emptyMap());
+ assertThat(httpServer.requestedPath()).isEqualTo("/api/bar");
+ }
+
+ protected static Date toDate(String sDate) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ return sdf.parse(sDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java
new file mode 100644
index 00000000000..c24be3ef64c
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java
@@ -0,0 +1,128 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+import static org.apache.commons.io.IOUtils.write;
+
+public class MockHttpServer {
+ private Server server;
+ private String responseBody;
+ private byte[] binaryResponseBody;
+ private int responseStatus = HttpURLConnection.HTTP_OK;
+ private String requestPath;
+ private Map requestHeaders = new HashMap(), requestParams = new HashMap();
+ private String contentType;
+
+ public void start() throws Exception {
+ // 0 is random available port
+ server = new Server(0);
+ server.setHandler(getMockHandler());
+ server.start();
+ }
+
+ public Handler getMockHandler() {
+ Handler handler = new AbstractHandler() {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest httpServletRequest, HttpServletResponse response) throws IOException, ServletException {
+ requestPath = baseRequest.getUri().toString();
+ requestHeaders.clear();
+ Enumeration names = baseRequest.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String headerName = (String) names.nextElement();
+ requestHeaders.put(headerName, baseRequest.getHeader(headerName));
+ }
+ requestParams.clear();
+ names = baseRequest.getParameterNames();
+ while (names.hasMoreElements()) {
+ String headerName = (String) names.nextElement();
+ requestParams.put(headerName, baseRequest.getParameter(headerName));
+ }
+ response.setStatus(responseStatus);
+ response.setContentType("application/json;charset=utf-8");
+ if (responseBody != null) {
+ write(responseBody, response.getOutputStream());
+ } else {
+ write(binaryResponseBody, response.getOutputStream());
+ }
+ baseRequest.setHandled(true);
+ }
+ };
+ return handler;
+ }
+
+ public void stop() {
+ try {
+ if (server != null) {
+ server.stop();
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to stop HTTP server", e);
+ }
+ }
+
+ public MockHttpServer doReturnBody(String responseBody) {
+ this.responseBody = responseBody;
+ return this;
+ }
+
+ public MockHttpServer doReturnBody(byte[] responseBody) {
+ this.binaryResponseBody = responseBody;
+ return this;
+ }
+
+ public MockHttpServer doReturnStatus(int status) {
+ this.responseStatus = status;
+ return this;
+ }
+
+ public MockHttpServer doReturnContentType(String contentType) {
+ this.contentType = contentType;
+ return this;
+ }
+
+ public String requestPath() {
+ return requestPath;
+ }
+
+ public Map requestHeaders() {
+ return requestHeaders;
+ }
+
+ public Map requestParams() {
+ return requestParams;
+ }
+
+ public int getPort() {
+ return server.getConnectors()[0].getLocalPort();
+ }
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java
new file mode 100644
index 00000000000..cd361262709
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.Map;
+
+public final class MockHttpServerInterceptor extends ExternalResource {
+
+ private MockHttpServer server;
+
+ @Override
+ protected final void before() throws Throwable {
+ server = new MockHttpServer();
+ server.start();
+ }
+
+ @Override
+ protected void after() {
+ server.stop();
+ }
+
+ public MockHttpServerInterceptor stubResponseBody(String body) {
+ server.doReturnBody(body);
+ return this;
+ }
+
+ public MockHttpServerInterceptor stubStatusCode(int status) {
+ server.doReturnStatus(status);
+ return this;
+ }
+
+ public String requestedPath() {
+ return server.requestPath();
+ }
+
+ public Map requestHeaders() {
+ return server.requestHeaders();
+ }
+
+ public Map requestParams() {
+ return server.requestParams();
+ }
+
+ public int port() {
+ return server.getPort();
+ }
+
+ public String url() {
+ return "http://localhost:" + port();
+ }
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java
new file mode 100644
index 00000000000..6a62da72401
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import com.google.common.net.HttpHeaders;
+import com.google.common.net.MediaType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonarqube.ws.WsComponents;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WsClientTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ MockHttpServer server;
+
+ WsClient underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ server = new MockHttpServer();
+ server.start();
+
+ underTest = WsClient.create("http://localhost:" + server.getPort());
+ }
+
+ @After
+ public void stopServer() {
+ if (server != null) {
+ server.stop();
+ }
+ }
+
+ @Test
+ public void return_protobuf_response() throws Exception {
+ server.doReturnBody(
+ WsComponents.WsSearchResponse
+ .newBuilder()
+ .addComponents(WsComponents.WsSearchResponse.Component.getDefaultInstance())
+ .build()
+ .toByteArray());
+ server.doReturnStatus(HTTP_OK);
+ server.doReturnContentType(MediaType.PROTOBUF.toString());
+
+ WsComponents.WsSearchResponse response = underTest.execute(
+ new WsRequest("api/components/search").setMediaType(WsRequest.MediaType.PROTOBUF),
+ WsComponents.WsSearchResponse.PARSER);
+
+ assertThat(response.getComponentsCount()).isEqualTo(1);
+ assertThat(server.requestHeaders().get(HttpHeaders.ACCEPT)).isEqualTo(MediaType.PROTOBUF.toString());
+ }
+
+ @Test
+ public void return_json_response() throws Exception {
+ String expectedResponse = "{\"key\":value}";
+ server.doReturnBody(expectedResponse);
+ server.doReturnStatus(HTTP_OK);
+ server.doReturnContentType(MediaType.JSON_UTF_8.toString());
+
+ String response = underTest.execute(new WsRequest("api/components/search"));
+
+ assertThat(response).isEqualTo(expectedResponse);
+ assertThat(server.requestHeaders().get(HttpHeaders.ACCEPT)).isEqualTo(MediaType.JSON_UTF_8.toString());
+ }
+
+ @Test
+ public void url_should_not_be_null() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Server URL must be set");
+
+ underTest.builder().build();
+ }
+
+ @Test
+ public void url_should_not_be_empty() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Server URL must be set");
+
+ underTest.create("");
+ }
+
+ @Test
+ public void test_default_configuration() throws Exception {
+ underTest = WsClient.create("http://localhost:9000");
+ assertThat(underTest.requestFactory.getBaseUrl()).isEqualTo("http://localhost:9000");
+ assertThat(underTest.requestFactory.getLogin()).isNull();
+ assertThat(underTest.requestFactory.getPassword()).isNull();
+ assertThat(underTest.requestFactory.getConnectTimeoutInMilliseconds()).isEqualTo(WsClient.DEFAULT_CONNECT_TIMEOUT_MILLISECONDS);
+ assertThat(underTest.requestFactory.getReadTimeoutInMilliseconds()).isEqualTo(WsClient.DEFAULT_READ_TIMEOUT_MILLISECONDS);
+ assertThat(underTest.requestFactory.getProxyHost()).isNull();
+ assertThat(underTest.requestFactory.getProxyPort()).isEqualTo(0);
+ assertThat(underTest.requestFactory.getProxyLogin()).isNull();
+ assertThat(underTest.requestFactory.getProxyPassword()).isNull();
+ }
+
+ @Test
+ public void test_custom_configuration() throws Exception {
+ underTest = WsClient.builder().url("http://localhost:9000")
+ .login("eric")
+ .password("pass")
+ .connectTimeoutMilliseconds(12345)
+ .readTimeoutMilliseconds(6789)
+ .proxy("localhost", 2052)
+ .proxyLogin("proxyLogin")
+ .proxyPassword("proxyPass")
+ .build();
+ assertThat(underTest.requestFactory.getBaseUrl()).isEqualTo("http://localhost:9000");
+ assertThat(underTest.requestFactory.getLogin()).isEqualTo("eric");
+ assertThat(underTest.requestFactory.getPassword()).isEqualTo("pass");
+ assertThat(underTest.requestFactory.getConnectTimeoutInMilliseconds()).isEqualTo(12345);
+ assertThat(underTest.requestFactory.getReadTimeoutInMilliseconds()).isEqualTo(6789);
+ assertThat(underTest.requestFactory.getProxyHost()).isEqualTo("localhost");
+ assertThat(underTest.requestFactory.getProxyPort()).isEqualTo(2052);
+ assertThat(underTest.requestFactory.getProxyLogin()).isEqualTo("proxyLogin");
+ assertThat(underTest.requestFactory.getProxyPassword()).isEqualTo("proxyPass");
+ }
+}