<module>dashboard-plugin</module>
<module>extension-lifecycle-plugin</module>
<module>global-property-change-plugin</module>
+ <module>issue-filter-plugin</module>
<module>l10n-fr-pack</module>
<module>license-plugin</module>
<module>oauth2-auth-plugin</module>
<module>sonar-fake-plugin</module>
<module>sonar-subcategories-plugin</module>
<module>ui-extensions-plugin</module>
- <module>issue-filter-plugin</module>
<module>posttask-plugin</module>
+ <module>ws-plugin</module>
</modules>
</project>
--- /dev/null
+<?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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>it-plugins</artifactId>
+ <version>5.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>ws-plugin</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>sonar-plugin</packaging>
+ <name>SonarQube Integration Tests :: Plugins :: Ws</name>
+ <description>Plugin for WS tests</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <version>${apiVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-ws</artifactId>
+ <version>${apiVersion}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <pluginClass>WsPlugin</pluginClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * 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.
+ */
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsCe;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.LocalWsClientFactory;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsResponse;
+
+public final class LocalCallWebService implements WebService {
+
+ private final LocalWsClientFactory wsClientFactory;
+
+ public LocalCallWebService(LocalWsClientFactory wsClientFactory) {
+ this.wsClientFactory = wsClientFactory;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController controller = context.createController("local_ws_call");
+ controller.createAction("protobuf_data").setHandler(new ProtobufHandler());
+ controller.createAction("json_data").setHandler(new JsonHandler());
+ controller.createAction("require_permission").setHandler(new RequirePermissionHandler());
+ controller.done();
+ }
+
+ private class ProtobufHandler implements RequestHandler {
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ WsClient client = wsClientFactory.newClient(request.getLocalConnector());
+
+ WsCe.TaskTypesWsResponse ceTaskTypes = client.ce().taskTypes();
+ response.stream().setStatus(ceTaskTypes.getTaskTypesCount() > 0 ? 200 : 500);
+ }
+ }
+
+ private class JsonHandler implements RequestHandler {
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ WsClient client = wsClientFactory.newClient(request.getLocalConnector());
+
+ WsResponse jsonResponse = client.wsConnector().call(new GetRequest("api/issues/search"));
+ boolean ok = jsonResponse.contentType().equals(MediaTypes.JSON)
+ && jsonResponse.isSuccessful()
+ && jsonResponse.content().contains("\"issues\":");
+ response.stream().setStatus(ok ? 200 : 500);
+ }
+ }
+
+ private class RequirePermissionHandler implements RequestHandler {
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ WsClient client = wsClientFactory.newClient(request.getLocalConnector());
+
+ WsResponse jsonResponse = client.wsConnector().call(new GetRequest("api/system/info"));
+
+ boolean ok = MediaTypes.JSON.equals(jsonResponse.contentType())
+ && jsonResponse.isSuccessful()
+ && jsonResponse.content().startsWith("{");
+ response.stream().setStatus(ok ? 200 : 500);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+import java.util.Arrays;
+import java.util.List;
+import org.sonar.api.SonarPlugin;
+import org.sonarqube.ws.client.WsClientFactories;
+
+public class WsPlugin extends SonarPlugin {
+ public List getExtensions() {
+ return Arrays.asList(LocalCallWebService.class, WsClientFactories.getLocal());
+ }
+}
import it.user.LocalAuthenticationTest;
import it.user.MyAccountPageTest;
import it.user.OAuth2IdentityProviderTest;
+import it.ws.WsLocalCallTest;
import org.junit.ClassRule;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
// ui
UiTest.class,
// ui extensions
- UiExtensionsTest.class
+ UiExtensionsTest.class,
+ WsLocalCallTest.class
})
public class Category4Suite {
// Used in UiExtensionsTest
.addPlugin(pluginArtifact("ui-extensions-plugin"))
+ // Used by WsLocalCallTest
+ .addPlugin(pluginArtifact("ws-plugin"))
.build();
}
import org.sonarqube.ws.WsUserTokens;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.HttpConnector;
-import org.sonarqube.ws.client.HttpWsClient;
import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsClientFactories;
import org.sonarqube.ws.client.WsResponse;
import org.sonarqube.ws.client.permission.AddGroupWsRequest;
import org.sonarqube.ws.client.permission.AddUserWsRequest;
userRule.createUser(login, name, null, password);
// authenticate
- WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
+ WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
assertThat(response.content()).isEqualTo("{\"valid\":true}");
}
WsUserTokens.GenerateWsResponse generateWsResponse = userTokensWsClient.generate(new GenerateWsRequest()
.setLogin(LOGIN)
.setName(tokenName));
- WsClient wsClient = new HttpWsClient(new HttpConnector.Builder()
+ WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
.url(ORCHESTRATOR.getServer().getUrl())
.token(generateWsResponse.getToken()).build());
// This check is failing because signup doesn't refresh the users ES index !
// Will be fixed by SONAR-7308
-// userRule.verifyUserExists("signuplogin", "SignUpName", null);
+ // userRule.verifyUserExists("signuplogin", "SignUpName", null);
}
@Test
"/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html",
// SONAR-2009
"/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html"
- ).build()).runOn(ORCHESTRATOR);
+ ).build()).runOn(ORCHESTRATOR);
setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", "true");
new SeleneseTest(Selenese.builder().setHtmlTestsInClasspath("force-authentication",
// SONAR-3473
"/user/LocalAuthenticationTest/force-authentication.html"
- ).build()).runOn(ORCHESTRATOR);
+ ).build()).runOn(ORCHESTRATOR);
}
@Test
--- /dev/null
+/*
+ * 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 it.ws;
+
+import com.sonar.orchestrator.Orchestrator;
+import it.Category4Suite;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.HttpConnector;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsClientFactories;
+import org.sonarqube.ws.client.WsResponse;
+import util.QaOnly;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests the ability for a web service to call another web services.
+ */
+@Category(QaOnly.class)
+public class WsLocalCallTest {
+
+ @ClassRule
+ public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR;
+
+ @Test
+ public void gets_protobuf() {
+ WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/protobuf_data"));
+ assertThat(response.isSuccessful()).isTrue();
+ }
+
+ @Test
+ public void gets_json() {
+ WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/json_data"));
+ assertThat(response.isSuccessful()).isTrue();
+ }
+
+ @Test
+ public void propagates_authorization_rights() {
+ WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
+ .url(orchestrator.getServer().getUrl())
+ .credentials("admin", "admin")
+ .build());
+ WsResponse response = wsClient.wsConnector().call(new GetRequest("local_ws_call/require_permission"));
+ assertThat(response.isSuccessful()).isTrue();
+ }
+
+ @Test
+ public void fails_if_requires_permissions() {
+ WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/require_permission"));
+
+ // this is not the unauthorized code as plugin forces it to 500
+ assertThat(response.code()).isEqualTo(500);
+ }
+
+ private static WsClient newAnonymousClient() {
+ return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(orchestrator.getServer().getUrl()).build());
+ }
+}
import org.sonar.wsclient.services.PropertyDeleteQuery;
import org.sonar.wsclient.services.PropertyUpdateQuery;
import org.sonarqube.ws.client.HttpConnector;
-import org.sonarqube.ws.client.HttpWsClient;
import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsClientFactories;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.FluentIterable.from;
public static WsClient newAdminWsClient(Orchestrator orchestrator) {
Server server = orchestrator.getServer();
- return new HttpWsClient(new HttpConnector.Builder()
+ return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
.url(server.getUrl())
.credentials(ADMIN_LOGIN, ADMIN_PASSWORD)
.build());
public static WsClient newWsClient(Orchestrator orchestrator) {
Server server = orchestrator.getServer();
- return new HttpWsClient(new HttpConnector.Builder()
+ return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
.url(server.getUrl())
.build());
}
--- /dev/null
+/*
+ * 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.sonar.server.ws;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.LocalConnector;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonarqube.ws.MediaTypes;
+
+public class DefaultLocalResponse implements Response, LocalConnector.LocalResponse {
+
+ private final InMemoryStream stream = new InMemoryStream();
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final Map<String, String> headers = Maps.newHashMap();
+
+ @Override
+ public int getStatus() {
+ return stream().status();
+ }
+
+ @Override
+ public String getMediaType() {
+ return stream().mediaType();
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return output.toByteArray();
+ }
+
+ public class InMemoryStream implements Response.Stream {
+ private String mediaType;
+
+ private int status = 200;
+
+ @CheckForNull
+ public String mediaType() {
+ return mediaType;
+ }
+
+ public int status() {
+ return status;
+ }
+
+ @Override
+ public Response.Stream setMediaType(String s) {
+ this.mediaType = s;
+ return this;
+ }
+
+ @Override
+ public Response.Stream setStatus(int i) {
+ this.status = i;
+ return this;
+ }
+
+ @Override
+ public OutputStream output() {
+ return output;
+ }
+
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ stream.setMediaType(MediaTypes.JSON);
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ stream.setMediaType(MediaTypes.XML);
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public InMemoryStream stream() {
+ return stream;
+ }
+
+ @Override
+ public Response noContent() {
+ stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT);
+ IOUtils.closeQuietly(output);
+ return this;
+ }
+
+ public String outputAsString() {
+ return new String(output.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public Response setHeader(String name, String value) {
+ headers.put(name, value);
+ return this;
+ }
+
+ @Override
+ public Collection<String> getHeaderNames() {
+ return headers.keySet();
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ public byte[] getFlushedOutput() {
+ try {
+ output.flush();
+ return output.toByteArray();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.server.ws;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.sonar.api.server.ws.LocalConnector;
+import org.sonar.api.server.ws.internal.ValidatingRequest;
+
+public class LocalRequestAdapter extends ValidatingRequest {
+
+ private final LocalConnector.LocalRequest localRequest;
+
+ public LocalRequestAdapter(LocalConnector.LocalRequest localRequest) {
+ this.localRequest = localRequest;
+ }
+
+ @Override
+ protected String readParam(String key) {
+ return localRequest.getParam(key);
+ }
+
+ @Override
+ protected InputStream readInputStreamParam(String key) {
+ String value = readParam(key);
+ if (value == null) {
+ return null;
+ }
+ return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return localRequest.hasParam(key);
+ }
+
+ @Override
+ public String method() {
+ return localRequest.getMethod();
+ }
+
+ @Override
+ public String getMediaType() {
+ return localRequest.getMediaType();
+ }
+}
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
+import org.apache.commons.lang.StringUtils;
import org.picocontainer.Startable;
import org.sonar.api.i18n.I18n;
import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.ws.LocalConnector;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.internal.ValidatingRequest;
import org.sonar.api.utils.log.Loggers;
* @since 4.2
*/
@ServerSide
-public class WebServiceEngine implements Startable {
+public class WebServiceEngine implements LocalConnector, Startable {
private final WebService.Context context;
private final I18n i18n;
return context.controllers();
}
- public void execute(ValidatingRequest request, ServletResponse response,
- String controllerPath, String actionKey) {
+ @Override
+ public LocalResponse call(LocalRequest request) {
+ String controller = StringUtils.substringBeforeLast(request.getPath(), "/");
+ String action = StringUtils.substringAfterLast(request.getPath(), "/");
+ DefaultLocalResponse localResponse = new DefaultLocalResponse();
+ execute(new LocalRequestAdapter(request), localResponse, controller, action);
+ return localResponse;
+ }
+
+ public void execute(Request request, Response response, String controllerPath, String actionKey) {
try {
WebService.Action action = getAction(controllerPath, actionKey);
- request.setAction(action);
+ if (request instanceof ValidatingRequest) {
+ ((ValidatingRequest) request).setAction(action);
+ ((ValidatingRequest) request).setLocalConnector(this);
+ }
verifyRequest(action, request);
action.handler().handle(request, response);
return action;
}
- private void sendErrors(ServletResponse response, int status, Errors errors) {
- ServletResponse.ServletStream stream = response.stream();
- stream.reset();
+ private void sendErrors(Response response, int status, Errors errors) {
+ Response.Stream stream = response.stream();
+ if (stream instanceof ServletResponse.ServletStream) {
+ ((ServletResponse.ServletStream) stream).reset();
+ }
stream.setStatus(status);
stream.setMediaType(MediaTypes.JSON);
JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8));
--- /dev/null
+/*
+ * 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.sonar.api.server.ws;
+
+import com.google.common.annotations.Beta;
+import java.util.Collection;
+import javax.annotation.CheckForNull;
+
+/**
+ * This class allows a web service to call another web service through the sonar-ws library.
+ * The call is in-process, synchronous and does not involve the HTTP stack.
+ * <p>
+ * Example of a web service that uses sonar-ws 5.5 to get some issues:
+ * <pre>
+ * import org.sonar.api.server.ws.RequestHandler;
+ * import org.sonarqube.ws.client.WsClientFactories;
+ *
+ * public class MyRequestHandler implements RequestHandler {
+ * {@literal @}Override
+ * public void handle(Request request, Response response) {
+ * WsClient wsClient = WsClientFactories.getLocal().newClient(request.getLocalConnector());
+ * SearchWsResponse issues = wsClient.issues().search(new SearchWsRequest());
+ * // ...
+ * }
+ * }
+ * </pre>
+ * </p>
+ * @since 5.5
+ */
+@Beta
+public interface LocalConnector {
+
+ LocalResponse call(LocalRequest request);
+
+ interface LocalRequest {
+ /**
+ * URL path, which is the concatenation of controller path and action key, for example "api/issues/search"
+ * @see org.sonar.api.server.ws.WebService.Controller#path
+ * @see org.sonar.api.server.ws.WebService.Action#key
+ */
+ String getPath();
+
+ /**
+ * @see Request#getMediaType()
+ */
+ String getMediaType();
+
+ /**
+ * HTTP method. Possible values are "GET" and "POST"
+ * @see Request#method()
+ */
+ String getMethod();
+
+ /**
+ * @see Request#hasParam(String)
+ */
+ boolean hasParam(String key);
+
+ /**
+ * @see Request#param(String)
+ */
+ @CheckForNull
+ String getParam(String key);
+ }
+
+ interface LocalResponse {
+ /**
+ * @see org.sonar.api.server.ws.Response.Stream#setStatus(int)
+ */
+ int getStatus();
+
+ /**
+ * @see org.sonar.api.server.ws.Response.Stream#setMediaType(String)
+ */
+ String getMediaType();
+
+ /**
+ * Response body
+ */
+ byte[] getBytes();
+
+ /**
+ * HTTP headers
+ * @see Response#setHeader(String, String)
+ */
+ Collection<String> getHeaderNames();
+
+ /**
+ * @see Response#setHeader(String, String)
+ * @return
+ */
+ @CheckForNull
+ String getHeader(String name);
+ }
+
+}
*/
package org.sonar.api.server.ws;
+import com.google.common.annotations.Beta;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import java.io.InputStream;
}
throw new IllegalArgumentException(String.format("Property %s is not a boolean value: %s", key, value));
}
+
+ /**
+ * Used by the sonar-ws library to allow a web service to call another web service.
+ * @see LocalConnector
+ * @since 5.5
+ */
+ @Beta
+ public abstract LocalConnector getLocalConnector();
}
import org.sonar.api.server.ServerSide;
/**
+ * Extension point to execute a HTTP request.
* @since 4.2
+ * @see WebService
*/
@ServerSide
@ExtensionPoint
interface Stream {
Stream setMediaType(String s);
+
+ /**
+ * HTTP status code. See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
+ * By default value is set to 200.
+ */
Stream setStatus(int httpStatus);
OutputStream output();
}
* }
* }
* </pre>
- * <h3>How to test</h3>
- * <pre>
- * public class HelloWsTest {
- * WebService ws = new HelloWs();
- *
- * {@literal @}Test
- * public void should_define_ws() throws Exception {
- * // WsTester is available in the Maven artifact org.codehaus.sonar:sonar-testing-harness
- * WsTester tester = new WsTester(ws);
- * WebService.Controller controller = tester.controller("api/hello");
- * assertThat(controller).isNotNull();
- * assertThat(controller.path()).isEqualTo("api/hello");
- * assertThat(controller.description()).isNotEmpty();
- * assertThat(controller.actions()).hasSize(1);
- *
- * WebService.Action show = controller.action("show");
- * assertThat(show).isNotNull();
- * assertThat(show.key()).isEqualTo("show");
- * assertThat(index.handler()).isNotNull();
- * }
- * }
- * </pre>
*
* @since 4.2
*/
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.LocalConnector;
import org.sonar.api.server.ws.Request;
import static com.google.common.base.Preconditions.checkNotNull;
return this;
}
+ public Map<String, String> getParams() {
+ return params;
+ }
+
+ @Override
+ public LocalConnector getLocalConnector() {
+ throw new UnsupportedOperationException();
+ }
}
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.sonar.api.server.ws.LocalConnector;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.log.Loggers;
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
* @since 4.2
*/
public abstract class ValidatingRequest extends Request {
private WebService.Action action;
+ private LocalConnector localConnector;
public void setAction(WebService.Action action) {
this.action = action;
return action;
}
+ @Override
+ public LocalConnector getLocalConnector() {
+ checkNotNull(localConnector, "Local connector has not been set");
+ return localConnector;
+ }
+
+ public void setLocalConnector(LocalConnector lc) {
+ this.localConnector = lc;
+ }
+
@Override
@CheckForNull
public String param(String key) {
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.WsClientFactories;
import static java.lang.Integer.parseInt;
import static java.lang.String.valueOf;
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();
+ HttpConnector.Builder connectorBuilder = HttpConnector.newBuilder();
// 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
+ connectorBuilder
.readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000)
.connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS)
.userAgent(env.toString())
.url(url)
.credentials(login, settings.property(CoreProperties.PASSWORD));
- wsClient = new BatchWsClient(new HttpWsClient(builder.build()), login != null);
+ wsClient = new BatchWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null);
}
return wsClient;
}
<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>
*/
package org.sonarqube.ws.client;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+
abstract class BaseResponse implements WsResponse {
@Override
return this;
}
+ @Override
+ public boolean hasContent() {
+ return code() != HTTP_NO_CONTENT;
+ }
}
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;
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);
}
--- /dev/null
+/*
+ * 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.sonarqube.ws.client.ce.CeService;
+import org.sonarqube.ws.client.component.ComponentsService;
+import org.sonarqube.ws.client.issue.IssuesService;
+import org.sonarqube.ws.client.measure.MeasuresService;
+import org.sonarqube.ws.client.permission.PermissionsService;
+import org.sonarqube.ws.client.qualitygate.QualityGatesService;
+import org.sonarqube.ws.client.qualityprofile.QualityProfilesService;
+import org.sonarqube.ws.client.rule.RulesService;
+import org.sonarqube.ws.client.system.SystemService;
+import org.sonarqube.ws.client.usertoken.UserTokensService;
+
+/**
+ * This class is not public anymore since version 5.5. It is
+ * created by {@link WsClientFactory}
+ *
+ * @since 5.3
+ */
+class DefaultWsClient implements WsClient {
+
+ private final WsConnector wsConnector;
+ private final PermissionsService permissionsService;
+ private final ComponentsService componentsService;
+ private final QualityProfilesService qualityProfilesService;
+ private final IssuesService issuesService;
+ private final UserTokensService userTokensService;
+ private final QualityGatesService qualityGatesService;
+ private final MeasuresService measuresService;
+ private final SystemService systemService;
+ private final CeService ceService;
+ private final RulesService rulesService;
+
+ DefaultWsClient(WsConnector wsConnector) {
+ this.wsConnector = wsConnector;
+ this.permissionsService = new PermissionsService(wsConnector);
+ this.componentsService = new ComponentsService(wsConnector);
+ this.qualityProfilesService = new QualityProfilesService(wsConnector);
+ this.issuesService = new IssuesService(wsConnector);
+ this.userTokensService = new UserTokensService(wsConnector);
+ this.qualityGatesService = new QualityGatesService(wsConnector);
+ this.measuresService = new MeasuresService(wsConnector);
+ this.systemService = new SystemService(wsConnector);
+ this.ceService = new CeService(wsConnector);
+ this.rulesService = new RulesService(wsConnector);
+ }
+
+ @Override
+ public WsConnector wsConnector() {
+ return wsConnector;
+ }
+
+ @Override
+ public PermissionsService permissions() {
+ return this.permissionsService;
+ }
+
+ @Override
+ public ComponentsService components() {
+ return componentsService;
+ }
+
+ @Override
+ public QualityProfilesService qualityProfiles() {
+ return qualityProfilesService;
+ }
+
+ @Override
+ public IssuesService issues() {
+ return issuesService;
+ }
+
+ @Override
+ public UserTokensService userTokens() {
+ return userTokensService;
+ }
+
+ @Override
+ public QualityGatesService qualityGates() {
+ return qualityGatesService;
+ }
+
+ @Override
+ public MeasuresService measures() {
+ return measuresService;
+ }
+
+ @Override
+ public SystemService system() {
+ return systemService;
+ }
+
+ @Override
+ public CeService ce() {
+ return ceService;
+ }
+
+ @Override
+ public RulesService rules() {
+ return rulesService;
+ }
+}
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;
private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS;
private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS;
+ /**
+ * Private since 5.5.
+ * @see HttpConnector#newBuilder()
+ */
+ private Builder() {
+ }
+
/**
* Optional User Agent
*/
+++ /dev/null
-/*
- * 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.squareup.okhttp.Response;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-
-import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
-
-class HttpResponse extends BaseResponse {
-
- private final Response okResponse;
-
- HttpResponse(Response okResponse) {
- this.okResponse = okResponse;
- }
-
- @Override
- public int code() {
- return okResponse.code();
- }
-
- @Override
- public String requestUrl() {
- return okResponse.request().urlString();
- }
-
- @Override
- public boolean hasContent() {
- return okResponse.code() != HTTP_NO_CONTENT;
- }
-
- @Override
- public String contentType() {
- return okResponse.header("Content-Type");
- }
-
- /**
- * Get stream of bytes
- */
- @Override
- public InputStream contentStream() {
- try {
- return okResponse.body().byteStream();
- } catch (IOException e) {
- throw fail(e);
- }
- }
-
- /**
- * Get stream of characters, decoded with the charset
- * of the Content-Type header. If that header is either absent or lacks a
- * charset, this will attempt to decode the response body as UTF-8.
- */
- @Override
- public Reader contentReader() {
- try {
- return okResponse.body().charStream();
- } catch (IOException e) {
- throw fail(e);
- }
- }
-
- @Override
- public String content() {
- try {
- return okResponse.body().string();
- } catch (IOException e) {
- throw fail(e);
- }
- }
-
- private RuntimeException fail(Exception e) {
- throw new IllegalStateException("Fail to read response of " + requestUrl(), e);
- }
-}
+++ /dev/null
-/*
- * 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.sonarqube.ws.client.ce.CeService;
-import org.sonarqube.ws.client.component.ComponentsService;
-import org.sonarqube.ws.client.issue.IssuesService;
-import org.sonarqube.ws.client.measure.MeasuresService;
-import org.sonarqube.ws.client.permission.PermissionsService;
-import org.sonarqube.ws.client.qualitygate.QualityGatesService;
-import org.sonarqube.ws.client.qualityprofile.QualityProfilesService;
-import org.sonarqube.ws.client.rule.RulesService;
-import org.sonarqube.ws.client.system.SystemService;
-import org.sonarqube.ws.client.usertoken.UserTokensService;
-
-/**
- * Entry point of the Java Client for SonarQube Web Services
- *
- * @since 5.3
- */
-public class HttpWsClient implements WsClient {
-
- private final WsConnector wsConnector;
- private final PermissionsService permissionsService;
- private final ComponentsService componentsService;
- private final QualityProfilesService qualityProfilesService;
- private final IssuesService issuesService;
- private final UserTokensService userTokensService;
- private final QualityGatesService qualityGatesService;
- private final MeasuresService measuresService;
- private final SystemService systemService;
- private final CeService ceService;
- private final RulesService rulesService;
-
- public HttpWsClient(WsConnector wsConnector) {
- this.wsConnector = wsConnector;
- this.permissionsService = new PermissionsService(wsConnector);
- this.componentsService = new ComponentsService(wsConnector);
- this.qualityProfilesService = new QualityProfilesService(wsConnector);
- this.issuesService = new IssuesService(wsConnector);
- this.userTokensService = new UserTokensService(wsConnector);
- this.qualityGatesService = new QualityGatesService(wsConnector);
- this.measuresService = new MeasuresService(wsConnector);
- this.systemService = new SystemService(wsConnector);
- this.ceService = new CeService(wsConnector);
- this.rulesService = new RulesService(wsConnector);
- }
-
- @Override
- public WsConnector wsConnector() {
- return wsConnector;
- }
-
- @Override
- public PermissionsService permissions() {
- return this.permissionsService;
- }
-
- @Override
- public ComponentsService components() {
- return componentsService;
- }
-
- @Override
- public QualityProfilesService qualityProfiles() {
- return qualityProfilesService;
- }
-
- @Override
- public IssuesService issues() {
- return issuesService;
- }
-
- @Override
- public UserTokensService userTokens() {
- return userTokensService;
- }
-
- @Override
- public QualityGatesService qualityGates() {
- return qualityGatesService;
- }
-
- @Override
- public MeasuresService measures() {
- return measuresService;
- }
-
- @Override
- public SystemService system() {
- return systemService;
- }
-
- @Override
- public CeService ce() {
- return ceService;
- }
-
- @Override
- public RulesService rules() {
- return rulesService;
- }
-}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.squareup.okhttp.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+class OkHttpResponse extends BaseResponse {
+
+ private final Response okResponse;
+
+ OkHttpResponse(Response okResponse) {
+ this.okResponse = okResponse;
+ }
+
+ @Override
+ public int code() {
+ return okResponse.code();
+ }
+
+ @Override
+ public String requestUrl() {
+ return okResponse.request().urlString();
+ }
+
+ @Override
+ public String contentType() {
+ return okResponse.header("Content-Type");
+ }
+
+ /**
+ * Get stream of bytes
+ */
+ @Override
+ public InputStream contentStream() {
+ try {
+ return okResponse.body().byteStream();
+ } catch (IOException e) {
+ throw fail(e);
+ }
+ }
+
+ /**
+ * Get stream of characters, decoded with the charset
+ * of the Content-Type header. If that header is either absent or lacks a
+ * charset, this will attempt to decode the response body as UTF-8.
+ */
+ @Override
+ public Reader contentReader() {
+ try {
+ return okResponse.body().charStream();
+ } catch (IOException e) {
+ throw fail(e);
+ }
+ }
+
+ @Override
+ public String content() {
+ try {
+ return okResponse.body().string();
+ } catch (IOException e) {
+ throw fail(e);
+ }
+ }
+
+ private RuntimeException fail(Exception e) {
+ throw new IllegalStateException("Fail to read response of " + requestUrl(), e);
+ }
+}
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 {
--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+}
@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);
@Test
public void use_basic_authentication() throws Exception {
answerHelloWorld();
- HttpConnector underTest = new HttpConnector.Builder()
+ HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.credentials("theLogin", "thePassword")
.build();
@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();
@Test
public void use_access_token() throws Exception {
answerHelloWorld();
- HttpConnector underTest = new HttpConnector.Builder()
+ HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.token("theToken")
.build();
@Test
public void use_proxy_authentication() throws Exception {
answerHelloWorld();
- HttpConnector underTest = new HttpConnector.Builder()
+ HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.proxyCredentials("theProxyLogin", "theProxyPassword")
.build();
@Test
public void override_timeouts() {
- HttpConnector underTest = new HttpConnector.Builder()
+ HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.readTimeoutMilliseconds(42)
.connectTimeoutMilliseconds(74)
@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();
@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();
.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
.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();
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);
@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();
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();
@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
@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());
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}