Browse Source

SONAR-6948 Ability for Java server extensions to call web services

tags/5.5-RC1
Simon Brandhof 8 years ago
parent
commit
a2be8c1b0c
34 changed files with 1170 additions and 74 deletions
  1. 2
    1
      it/it-plugins/pom.xml
  2. 43
    0
      it/it-plugins/ws-plugin/pom.xml
  3. 85
    0
      it/it-plugins/ws-plugin/src/main/java/LocalCallWebService.java
  4. 30
    0
      it/it-plugins/ws-plugin/src/main/java/WsPlugin.java
  5. 5
    1
      it/it-tests/src/test/java/it/Category4Suite.java
  6. 6
    6
      it/it-tests/src/test/java/it/user/LocalAuthenticationTest.java
  7. 78
    0
      it/it-tests/src/test/java/it/ws/WsLocalCallTest.java
  8. 3
    3
      it/it-tests/src/test/java/util/ItUtils.java
  9. 146
    0
      server/sonar-server/src/main/java/org/sonar/server/ws/DefaultLocalResponse.java
  10. 64
    0
      server/sonar-server/src/main/java/org/sonar/server/ws/LocalRequestAdapter.java
  11. 24
    7
      server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java
  12. BIN
      server/sonar-web/npm.tar.gz
  13. 113
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/LocalConnector.java
  14. 9
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java
  15. 2
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java
  16. 5
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Response.java
  17. 0
    22
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
  18. 9
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/SimpleGetRequest.java
  19. 14
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/ValidatingRequest.java
  20. 4
    4
      sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java
  21. 6
    0
      sonar-ws/pom.xml
  22. 6
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java
  23. 3
    1
      sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java
  24. 4
    3
      sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java
  25. 16
    2
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java
  26. 30
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsClientFactory.java
  27. 132
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsConnector.java
  28. 2
    9
      sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpResponse.java
  29. 15
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
  30. 71
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactories.java
  31. 31
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactory.java
  32. 15
    15
      sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java
  33. 150
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/LocalWsConnectorTest.java
  34. 47
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientFactoriesTest.java

+ 2
- 1
it/it-plugins/pom.xml View File

@@ -36,6 +36,7 @@
<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>
@@ -50,7 +51,7 @@
<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>

+ 43
- 0
it/it-plugins/ws-plugin/pom.xml View File

@@ -0,0 +1,43 @@
<?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>

+ 85
- 0
it/it-plugins/ws-plugin/src/main/java/LocalCallWebService.java View File

@@ -0,0 +1,85 @@
/*
* 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);
}
}
}

+ 30
- 0
it/it-plugins/ws-plugin/src/main/java/WsPlugin.java View File

@@ -0,0 +1,30 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

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());
}
}

+ 5
- 1
it/it-tests/src/test/java/it/Category4Suite.java View File

@@ -41,6 +41,7 @@ import it.user.ForceAuthenticationTest;
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;
@@ -83,7 +84,8 @@ import static util.ItUtils.xooPlugin;
// ui
UiTest.class,
// ui extensions
UiExtensionsTest.class
UiExtensionsTest.class,
WsLocalCallTest.class
})
public class Category4Suite {

@@ -104,5 +106,7 @@ public class Category4Suite {
// Used in UiExtensionsTest
.addPlugin(pluginArtifact("ui-extensions-plugin"))

// Used by WsLocalCallTest
.addPlugin(pluginArtifact("ws-plugin"))
.build();
}

+ 6
- 6
it/it-tests/src/test/java/it/user/LocalAuthenticationTest.java View File

@@ -37,8 +37,8 @@ import org.junit.experimental.categories.Category;
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;
@@ -109,7 +109,7 @@ public class LocalAuthenticationTest {
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}");
}
@@ -120,7 +120,7 @@ public class LocalAuthenticationTest {
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());

@@ -196,7 +196,7 @@ public class LocalAuthenticationTest {

// 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
@@ -208,14 +208,14 @@ public class LocalAuthenticationTest {
"/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

+ 78
- 0
it/it-tests/src/test/java/it/ws/WsLocalCallTest.java View File

@@ -0,0 +1,78 @@
/*
* 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());
}
}

+ 3
- 3
it/it-tests/src/test/java/util/ItUtils.java View File

@@ -49,8 +49,8 @@ import org.sonar.wsclient.issue.IssueQuery;
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;
@@ -77,7 +77,7 @@ public class ItUtils {

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());
@@ -85,7 +85,7 @@ public class ItUtils {

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());
}

+ 146
- 0
server/sonar-server/src/main/java/org/sonar/server/ws/DefaultLocalResponse.java View File

@@ -0,0 +1,146 @@
/*
* 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);
}
}
}

+ 64
- 0
server/sonar-server/src/main/java/org/sonar/server/ws/LocalRequestAdapter.java View File

@@ -0,0 +1,64 @@
/*
* 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();
}
}

+ 24
- 7
server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java View File

@@ -22,9 +22,13 @@ package org.sonar.server.ws;
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;
@@ -43,7 +47,7 @@ import static org.sonar.server.ws.RequestVerifier.verifyRequest;
* @since 4.2
*/
@ServerSide
public class WebServiceEngine implements Startable {
public class WebServiceEngine implements LocalConnector, Startable {

private final WebService.Context context;
private final I18n i18n;
@@ -76,11 +80,22 @@ public class WebServiceEngine implements Startable {
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);

@@ -112,9 +127,11 @@ public class WebServiceEngine implements Startable {
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));

BIN
server/sonar-web/npm.tar.gz View File


+ 113
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/LocalConnector.java View File

@@ -0,0 +1,113 @@
/*
* 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);
}

}

+ 9
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java View File

@@ -19,6 +19,7 @@
*/
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;
@@ -234,4 +235,12 @@ public abstract class Request {
}
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();
}

+ 2
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java View File

@@ -23,7 +23,9 @@ import org.sonar.api.ExtensionPoint;
import org.sonar.api.server.ServerSide;

/**
* Extension point to execute a HTTP request.
* @since 4.2
* @see WebService
*/
@ServerSide
@ExtensionPoint

+ 5
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Response.java View File

@@ -34,6 +34,11 @@ public interface Response {

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();
}

+ 0
- 22
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java View File

@@ -87,28 +87,6 @@ import static java.lang.String.format;
* }
* }
* </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
*/

+ 9
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/SimpleGetRequest.java View File

@@ -24,6 +24,7 @@ import java.io.InputStream;
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;
@@ -76,4 +77,12 @@ public class SimpleGetRequest extends Request {
return this;
}

public Map<String, String> getParams() {
return params;
}

@Override
public LocalConnector getLocalConnector() {
throw new UnsupportedOperationException();
}
}

+ 14
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/ValidatingRequest.java View File

@@ -29,16 +29,20 @@ import java.util.Set;
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;
@@ -48,6 +52,16 @@ public abstract class ValidatingRequest extends Request {
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) {

+ 4
- 4
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java View File

@@ -24,7 +24,7 @@ import org.sonar.api.CoreProperties;
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;
@@ -42,20 +42,20 @@ public class BatchWsClientProvider extends ProviderAdapter {
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;
}

+ 6
- 0
sonar-ws/pom.xml View File

@@ -39,6 +39,12 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<!-- Tests -->
<dependency>

+ 6
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java View File

@@ -19,6 +19,8 @@
*/
package org.sonarqube.ws.client;

import static java.net.HttpURLConnection.HTTP_NO_CONTENT;

abstract class BaseResponse implements WsResponse {

@Override
@@ -34,4 +36,8 @@ abstract class BaseResponse implements WsResponse {
return this;
}

@Override
public boolean hasContent() {
return code() != HTTP_NO_CONTENT;
}
}

+ 3
- 1
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java View File

@@ -26,6 +26,7 @@ import java.io.InputStream;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.sonarqube.ws.MediaTypes;

import static com.google.common.base.Preconditions.checkArgument;
@@ -56,8 +57,9 @@ public abstract class BaseService {

public <T extends Message> T convert(WsResponse response, Parser<T> parser) {
try (InputStream byteStream = response.contentStream()) {
byte[] bytes = IOUtils.toByteArray(byteStream);
// HTTP header "Content-Type" is not verified. It may be different than protobuf.
return parser.parseFrom(byteStream);
return parser.parseFrom(bytes);
} catch (Exception e) {
throw new IllegalStateException("Fail to parse protobuf response of " + response.requestUrl(), e);
}

sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java View File

@@ -31,11 +31,12 @@ import org.sonarqube.ws.client.system.SystemService;
import org.sonarqube.ws.client.usertoken.UserTokensService;

/**
* Entry point of the Java Client for SonarQube Web Services
* This class is not public anymore since version 5.5. It is
* created by {@link WsClientFactory}
*
* @since 5.3
*/
public class HttpWsClient implements WsClient {
class DefaultWsClient implements WsClient {

private final WsConnector wsConnector;
private final PermissionsService permissionsService;
@@ -49,7 +50,7 @@ public class HttpWsClient implements WsClient {
private final CeService ceService;
private final RulesService rulesService;

public HttpWsClient(WsConnector wsConnector) {
DefaultWsClient(WsConnector wsConnector) {
this.wsConnector = wsConnector;
this.permissionsService = new PermissionsService(wsConnector);
this.componentsService = new ComponentsService(wsConnector);

+ 16
- 2
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java View File

@@ -202,16 +202,23 @@ public class HttpConnector implements WsConnector {
return okHttpRequestBuilder;
}

private HttpResponse doCall(Request okRequest) {
private OkHttpResponse doCall(Request okRequest) {
Call call = okHttpClient.newCall(okRequest);
try {
Response okResponse = call.execute();
return new HttpResponse(okResponse);
return new OkHttpResponse(okResponse);
} catch (IOException e) {
throw new IllegalStateException("Fail to request " + okRequest.urlString(), e);
}
}

/**
* @since 5.5
*/
public static Builder newBuilder() {
return new Builder();
}

public static class Builder {
private String url;
private String userAgent;
@@ -223,6 +230,13 @@ public class HttpConnector implements WsConnector {
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
*/

+ 30
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsClientFactory.java View File

@@ -0,0 +1,30 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import org.sonar.api.server.ServerSide;
import org.sonar.api.server.ws.LocalConnector;

@ServerSide
public interface LocalWsClientFactory extends WsClientFactory {

WsClient newClient(LocalConnector localConnector);

}

+ 132
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/LocalWsConnector.java View File

@@ -0,0 +1,132 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import com.google.common.annotations.VisibleForTesting;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.sonar.api.server.ws.LocalConnector;

import static java.nio.charset.StandardCharsets.UTF_8;

class LocalWsConnector implements WsConnector {

private final LocalConnector localConnector;

LocalWsConnector(LocalConnector localConnector) {
this.localConnector = localConnector;
}

@VisibleForTesting
LocalConnector getLocalConnector() {
return localConnector;
}

@Override
public String baseUrl() {
return "/";
}

@Override
public WsResponse call(WsRequest wsRequest) {
DefaultLocalRequest localRequest = new DefaultLocalRequest(wsRequest);
LocalConnector.LocalResponse localResponse = localConnector.call(localRequest);
return new ByteArrayResponse(wsRequest.getPath(), localResponse);
}

private static class DefaultLocalRequest implements LocalConnector.LocalRequest {
private final WsRequest wsRequest;

public DefaultLocalRequest(WsRequest wsRequest) {
this.wsRequest = wsRequest;
}

@Override
public String getPath() {
return wsRequest.getPath();
}

@Override
public String getMediaType() {
return wsRequest.getMediaType();
}

@Override
public String getMethod() {
return wsRequest.getMethod().name();
}

@Override
public boolean hasParam(String key) {
return wsRequest.getParams().containsKey(key);
}

@Override
public String getParam(String key) {
return wsRequest.getParams().get(key);
}
}

private static class ByteArrayResponse extends BaseResponse {
private final String path;
private final byte[] bytes;
private final String contentType;
private final int code;

ByteArrayResponse(String path, LocalConnector.LocalResponse localResponse) {
this.path = path;
this.bytes = localResponse.getBytes();
this.contentType = localResponse.getMediaType();
this.code = localResponse.getStatus();
}

@Override
public String requestUrl() {
return path;
}

@Override
public int code() {
return code;
}

@Override
public String contentType() {
return contentType;
}

@Override
public InputStream contentStream() {
return new ByteArrayInputStream(bytes);
}

@Override
public Reader contentReader() {
return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8);
}

@Override
public String content() {
return new String(bytes, UTF_8);
}
}
}

sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java → sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpResponse.java View File

@@ -24,13 +24,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import static java.net.HttpURLConnection.HTTP_NO_CONTENT;

class HttpResponse extends BaseResponse {
class OkHttpResponse extends BaseResponse {

private final Response okResponse;

HttpResponse(Response okResponse) {
OkHttpResponse(Response okResponse) {
this.okResponse = okResponse;
}

@@ -44,11 +42,6 @@ class HttpResponse extends BaseResponse {
return okResponse.request().urlString();
}

@Override
public boolean hasContent() {
return okResponse.code() != HTTP_NO_CONTENT;
}

@Override
public String contentType() {
return okResponse.header("Content-Type");

+ 15
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java View File

@@ -31,6 +31,21 @@ import org.sonarqube.ws.client.system.SystemService;
import org.sonarqube.ws.client.usertoken.UserTokensService;

/**
* Allows to request the web services of SonarQube server. Instance is provided by
* {@link WsClientFactory}.
*
* <p>
* Usage:
* <pre>
* HttpConnector httpConnector = HttpConnector.newBuilder()
* .url("http://localhost:9000")
* .credentials("admin", "admin")
* .build();
* WsClient wsClient = WsClientFactories.getDefault().newClient(httpConnector);
* wsClient.issues().search(issueRequest);
* </pre>
* </p>
*
* @since 5.3
*/
public interface WsClient {

+ 71
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactories.java View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import org.sonar.api.server.ws.LocalConnector;

/**
* All provided implementations of {@link WsClientFactory}.
*/
public class WsClientFactories {

private WsClientFactories() {
// prevent instantiation
}

/**
* Factory to be used when connecting to a remote SonarQube web server.
*/
public static WsClientFactory getDefault() {
return DefaultWsClientFactory.INSTANCE;
}

/**
* Factory that allows a SonarQube web service to interact
* with other web services, without using the HTTP stack.
* @see org.sonar.api.server.ws.LocalConnector
*/
public static LocalWsClientFactory getLocal() {
return DefaultLocalWsClientFactory.INSTANCE;
}

private enum DefaultWsClientFactory implements WsClientFactory {
INSTANCE;

@Override
public WsClient newClient(WsConnector connector) {
return new DefaultWsClient(connector);
}
}

private enum DefaultLocalWsClientFactory implements LocalWsClientFactory {
INSTANCE;

@Override
public WsClient newClient(WsConnector connector) {
return DefaultWsClientFactory.INSTANCE.newClient(connector);
}

@Override
public WsClient newClient(LocalConnector localConnector) {
return new DefaultWsClient(new LocalWsConnector(localConnector));
}
}
}

+ 31
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClientFactory.java View File

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

/**
* Creates {@link WsClient}. Implementations are provided by {@link WsClientFactories}.
*
* @since 5.5
*/
public interface WsClientFactory {

WsClient newClient(WsConnector connector);

}

+ 15
- 15
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java View File

@@ -60,7 +60,7 @@ public class HttpConnectorTest {
@Test
public void test_default_settings() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build();
assertThat(underTest.baseUrl()).isEqualTo(serverUrl);
GetRequest request = new GetRequest("api/issues/search").setMediaType(MediaTypes.PROTOBUF);
WsResponse response = underTest.call(request);
@@ -87,7 +87,7 @@ public class HttpConnectorTest {
@Test
public void use_basic_authentication() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.credentials("theLogin", "thePassword")
.build();
@@ -102,7 +102,7 @@ public class HttpConnectorTest {
@Test
public void use_basic_authentication_with_null_password() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.credentials("theLogin", null)
.build();
@@ -121,7 +121,7 @@ public class HttpConnectorTest {
@Test
public void use_access_token() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.token("theToken")
.build();
@@ -136,7 +136,7 @@ public class HttpConnectorTest {
@Test
public void use_proxy_authentication() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.proxyCredentials("theProxyLogin", "theProxyPassword")
.build();
@@ -150,7 +150,7 @@ public class HttpConnectorTest {

@Test
public void override_timeouts() {
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.readTimeoutMilliseconds(42)
.connectTimeoutMilliseconds(74)
@@ -163,7 +163,7 @@ public class HttpConnectorTest {
@Test
public void send_user_agent() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
HttpConnector underTest = HttpConnector.newBuilder()
.url(serverUrl)
.userAgent("Maven Plugin/2.3")
.build();
@@ -176,7 +176,7 @@ public class HttpConnectorTest {

@Test
public void fail_if_unknown_implementation_of_request() {
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build();
try {
underTest.call(mock(WsRequest.class));
fail();
@@ -192,7 +192,7 @@ public class HttpConnectorTest {
.setParam("severity", "MAJOR")
.setMediaType(MediaTypes.PROTOBUF);

HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build();
WsResponse response = underTest.call(request);

// verify response
@@ -215,7 +215,7 @@ public class HttpConnectorTest {
.setPart("report", new PostRequest.Part(MediaTypes.TXT, reportFile))
.setMediaType(MediaTypes.PROTOBUF);

HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build();
WsResponse response = underTest.call(request);

assertThat(response.hasContent()).isTrue();
@@ -233,7 +233,7 @@ public class HttpConnectorTest {
public void http_error() throws Exception {
server.enqueue(new MockResponse().setResponseCode(404));
PostRequest request = new PostRequest("api/issues/search");
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build();

WsResponse wsResponse = underTest.call(request);
assertThat(wsResponse.code()).isEqualTo(404);
@@ -242,7 +242,7 @@ public class HttpConnectorTest {
@Test
public void support_base_url_ending_with_slash() throws Exception {
assertThat(serverUrl).endsWith("/");
HttpConnector underTest = new HttpConnector.Builder().url(StringUtils.removeEnd(serverUrl, "/")).build();
HttpConnector underTest = HttpConnector.newBuilder().url(StringUtils.removeEnd(serverUrl, "/")).build();
GetRequest request = new GetRequest("api/issues/search");

answerHelloWorld();
@@ -255,7 +255,7 @@ public class HttpConnectorTest {
public void support_base_url_with_context() {
// just to be sure
assertThat(serverUrl).endsWith("/");
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl + "sonar").build();
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl + "sonar").build();

GetRequest request = new GetRequest("api/issues/search");
answerHelloWorld();
@@ -269,7 +269,7 @@ public class HttpConnectorTest {
@Test
public void support_tls_1_2_on_java7() {
when(javaVersion.isJava7()).thenReturn(true);
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion);
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion);

assertTlsAndClearTextSpecifications(underTest);
// enable TLS 1.0, 1.1 and 1.2
@@ -279,7 +279,7 @@ public class HttpConnectorTest {
@Test
public void support_tls_versions_of_java8() {
when(javaVersion.isJava7()).thenReturn(false);
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion);
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion);

assertTlsAndClearTextSpecifications(underTest);
assertThat(underTest.okHttpClient().getSslSocketFactory()).isInstanceOf(SSLSocketFactory.getDefault().getClass());

+ 150
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/LocalWsConnectorTest.java View File

@@ -0,0 +1,150 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Test;
import org.sonar.api.server.ws.LocalConnector;
import org.sonarqube.ws.MediaTypes;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class LocalWsConnectorTest {

LocalConnector connector = mock(LocalConnector.class);
LocalWsConnector underTest = new LocalWsConnector(connector);

@Test
public void baseUrl_is_always_slash() {
assertThat(underTest.baseUrl()).isEqualTo("/");
}

@Test
public void call_request() throws Exception {
WsRequest wsRequest = new PostRequest("api/issues/search")
.setMediaType(MediaTypes.JSON)
.setParam("foo", "bar");
answer(new DumbLocalResponse(400, MediaTypes.JSON, "{}".getBytes(UTF_8), Collections.<String>emptyList()));

WsResponse wsResponse = underTest.call(wsRequest);

verifyRequested("POST", "api/issues/search", MediaTypes.JSON, ImmutableMap.of("foo", "bar"));
assertThat(wsResponse.code()).isEqualTo(400);
assertThat(wsResponse.content()).isEqualTo("{}");
assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo("{}");
assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo("{}");
assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON);
assertThat(wsResponse.requestUrl()).isEqualTo("api/issues/search");
}

@Test
public void call_request_with_defaults() throws Exception {
// no parameters, no media type
WsRequest wsRequest = new GetRequest("api/issues/search");
answer(new DumbLocalResponse(200, MediaTypes.JSON, "".getBytes(UTF_8), Collections.<String>emptyList()));

WsResponse wsResponse = underTest.call(wsRequest);

verifyRequested("GET", "api/issues/search", MediaTypes.JSON, Collections.<String, String>emptyMap());
assertThat(wsResponse.code()).isEqualTo(200);
assertThat(wsResponse.content()).isEqualTo("");
assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo("");
assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo("");
assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON);
}

private void answer(DumbLocalResponse response) {
when(connector.call(any(LocalConnector.LocalRequest.class))).thenReturn(response);
}

private void verifyRequested(final String expectedMethod, final String expectedPath,
final String expectedMediaType,
final Map<String, String> expectedParams) {
verify(connector).call(argThat(new TypeSafeMatcher<LocalConnector.LocalRequest>() {
@Override
protected boolean matchesSafely(LocalConnector.LocalRequest localRequest) {
boolean ok = localRequest.getMethod().equals(expectedMethod) && localRequest.getPath().equals(expectedPath);
ok &= localRequest.getMediaType().equals(expectedMediaType);
for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) {
String paramKey = expectedParam.getKey();
ok &= localRequest.hasParam(paramKey);
ok &= expectedParam.getValue().equals(localRequest.getParam(paramKey));
}
return ok;
}

@Override
public void describeTo(Description description) {
}
}));
}

private static class DumbLocalResponse implements LocalConnector.LocalResponse {
private final int code;
private final String mediaType;
private final byte[] bytes;
private final List<String> headers;

public DumbLocalResponse(int code, String mediaType, byte[] bytes, List<String> headers) {
this.code = code;
this.mediaType = mediaType;
this.bytes = bytes;
this.headers = headers;
}

@Override
public int getStatus() {
return code;
}

@Override
public String getMediaType() {
return mediaType;
}

@Override
public byte[] getBytes() {
return bytes;
}

@Override
public Collection<String> getHeaderNames() {
return headers;
}

@Override
public String getHeader(String name) {
throw new UnsupportedOperationException();
}
}
}

+ 47
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientFactoriesTest.java View File

@@ -0,0 +1,47 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import org.junit.Test;
import org.sonar.api.server.ws.LocalConnector;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

public class WsClientFactoriesTest {
@Test
public void create_http_client() {
HttpConnector connector = HttpConnector.newBuilder().url("http://localhost:9000").build();
WsClient client = WsClientFactories.getDefault().newClient(connector);

assertThat(client).isInstanceOf(DefaultWsClient.class);
assertThat(client.wsConnector()).isSameAs(connector);
}

@Test
public void create_local_client() {
LocalConnector connector = mock(LocalConnector.class);
WsClient client = WsClientFactories.getLocal().newClient(connector);

assertThat(client).isInstanceOf(DefaultWsClient.class);
assertThat(client.wsConnector()).isInstanceOf(LocalWsConnector.class);
assertThat(((LocalWsConnector) client.wsConnector()).getLocalConnector()).isSameAs(connector);
}
}

Loading…
Cancel
Save