Sfoglia il codice sorgente

SONAR-7054 use ws-client in batch

tags/5.3-RC1
Simon Brandhof 8 anni fa
parent
commit
bba16edb77
78 ha cambiato i file con 2350 aggiunte e 2038 eliminazioni
  1. 2
    2
      it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java
  2. 27
    26
      it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java
  3. 2
    2
      it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java
  4. 12
    12
      it/it-tests/src/test/java/it/authorisation/PermissionTest.java
  5. 1
    1
      it/it-tests/src/test/java/it/issue/CommonRulesTest.java
  6. 1
    1
      it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java
  7. 8
    8
      it/it-tests/src/test/java/util/ItUtils.java
  8. 5
    10
      pom.xml
  9. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java
  10. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java
  11. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java
  12. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java
  13. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java
  14. 12
    12
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java
  15. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java
  16. 12
    12
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java
  17. 6
    4
      sonar-batch-protocol/pom.xml
  18. 0
    5
      sonar-batch/pom.xml
  19. 3
    3
      sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java
  20. 16
    6
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
  21. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
  22. 0
    168
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
  23. 90
    0
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java
  24. 63
    0
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java
  25. 3
    4
      sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java
  26. 26
    33
      sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
  27. 80
    136
      sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java
  28. 3
    2
      sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java
  29. 4
    8
      sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java
  30. 3
    10
      sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
  31. 10
    11
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
  32. 0
    138
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java
  33. 129
    0
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java
  34. 81
    0
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java
  35. 8
    13
      sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java
  36. 6
    8
      sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java
  37. 5
    10
      sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
  38. 60
    103
      sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
  39. 0
    111
      sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java
  40. 71
    77
      sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java
  41. 7
    10
      sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java
  42. 18
    26
      sonar-ws/pom.xml
  43. 2
    1
      sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java
  44. 75
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java
  45. 63
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java
  46. 34
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java
  47. 182
    65
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java
  48. 10
    7
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java
  49. 0
    229
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java
  50. 90
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java
  51. 89
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java
  52. 108
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java
  53. 28
    30
      sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java
  54. 15
    59
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
  55. 17
    5
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java
  56. 9
    64
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java
  57. 41
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java
  58. 44
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java
  59. 71
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java
  60. 25
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java
  61. 12
    19
      sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java
  62. 1
    1
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java
  63. 8
    12
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java
  64. 55
    68
      sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsService.java
  65. 1
    1
      sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java
  66. 8
    12
      sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java
  67. 9
    33
      sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensService.java
  68. 1
    1
      sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java
  69. 84
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java
  70. 93
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java
  71. 297
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java
  72. 1
    1
      sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java
  73. 0
    86
      sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java
  74. 0
    128
      sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java
  75. 60
    0
      sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java
  76. 0
    151
      sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java
  77. 0
    80
      sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java
  78. 29
    0
      sonar-ws/src/test/protobuf/ws-testing.proto

+ 2
- 2
it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java Vedi File

@@ -137,7 +137,7 @@ public class ActionPlanTest {
try {
// Create a user having admin permission on the project
adminClient.userClient().create(UserParameters.create().login(projectAdminUser).name(projectAdminUser).password("password").passwordConfirmation("password"));
adminWsClient.permissionsClient().addUser(
adminWsClient.permissions().addUser(
new AddUserWsRequest()
.setLogin(projectAdminUser)
.setProjectKey(PROJECT_KEY)
@@ -145,7 +145,7 @@ public class ActionPlanTest {

// Create a user having browse permission on the project
adminClient.userClient().create(UserParameters.create().login(projectUser).name(projectUser).password("password").passwordConfirmation("password"));
adminWsClient.permissionsClient().addUser(
adminWsClient.permissions().addUser(
new AddUserWsRequest()
.setLogin(projectUser)
.setProjectKey(PROJECT_KEY)

+ 27
- 26
it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java Vedi File

@@ -32,18 +32,20 @@ import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
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.PostRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsResponse;
import org.sonarqube.ws.client.permission.AddGroupWsRequest;
import org.sonarqube.ws.client.permission.AddUserWsRequest;
import org.sonarqube.ws.client.permission.RemoveGroupWsRequest;
import org.sonarqube.ws.client.usertoken.GenerateWsRequest;
import org.sonarqube.ws.client.usertoken.UserTokensWsClient;
import org.sonarqube.ws.client.usertoken.UserTokensService;

import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonarqube.ws.client.HttpConnector.newHttpConnector;
import static org.sonarqube.ws.client.WsRequest.newGetRequest;
import static org.sonarqube.ws.client.WsRequest.newPostRequest;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;

@@ -51,7 +53,7 @@ public class AuthenticationTest {
@ClassRule
public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;
private static WsClient adminWsClient;
private static UserTokensWsClient userTokensWsClient;
private static UserTokensService userTokensWsClient;

private static final String PROJECT_KEY = "sample";
private static final String LOGIN = "george.orwell";
@@ -64,7 +66,7 @@ public class AuthenticationTest {
ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");

adminWsClient = newAdminWsClient(ORCHESTRATOR);
userTokensWsClient = adminWsClient.userTokensWsClient();
userTokensWsClient = adminWsClient.userTokens();
removeGroupPermission("anyone", "dryRunScan");
removeGroupPermission("anyone", "scan");

@@ -89,9 +91,9 @@ public class AuthenticationTest {
createUser(login, name, password);

// authenticate
WsClient wsClient = new WsClient(newHttpConnector().url(ORCHESTRATOR.getServer().getUrl()).login(login).password(password).build());
String response = wsClient.execute(newGetRequest("api/authentication/validate"));
assertThat(response).isEqualTo("{\"valid\":true}");
WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
assertThat(response.getContent()).isEqualTo("{\"valid\":true}");
}

@Test
@@ -99,14 +101,13 @@ public class AuthenticationTest {
WsUserTokens.GenerateWsResponse generateWsResponse = userTokensWsClient.generate(new GenerateWsRequest()
.setLogin(LOGIN)
.setName("Validate token based authentication"));
WsClient wsClient = new WsClient(newHttpConnector()
WsClient wsClient = new HttpWsClient(new HttpConnector.Builder()
.url(ORCHESTRATOR.getServer().getUrl())
.login(generateWsResponse.getToken())
.password("").build());
.token(generateWsResponse.getToken()).build());

String response = wsClient.execute(newGetRequest("api/authentication/validate"));
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));

assertThat(response).isEqualTo("{\"valid\":true}");
assertThat(response.getContent()).isEqualTo("{\"valid\":true}");
}

/**
@@ -123,9 +124,9 @@ public class AuthenticationTest {
createUser(login, format("name-%s", userId), password);

// authenticate
WsClient wsClient = new WsClient(newHttpConnector().url(ORCHESTRATOR.getServer().getUrl()).login(login).password(password).build());
String response = wsClient.execute(newGetRequest("api/authentication/validate"));
assertThat(response).isEqualTo("{\"valid\":false}");
WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
assertThat(response.getContent()).isEqualTo("{\"valid\":false}");
}

@Test
@@ -162,41 +163,41 @@ public class AuthenticationTest {
}

private static void createUser(String login, String password) {
adminWsClient.execute(
newPostRequest("api/users/create")
adminWsClient.wsConnector().call(
new PostRequest("api/users/create")
.setParam("login", login)
.setParam("name", login)
.setParam("password", password));
}

private static void createUser(String login, String name, String password) {
adminWsClient.execute(
newPostRequest("api/users/create")
adminWsClient.wsConnector().call(
new PostRequest("api/users/create")
.setParam("login", login)
.setParam("name", name)
.setParam("password", password));
}

private static void addUserPermission(String login, String permission) {
adminWsClient.permissionsClient().addUser(new AddUserWsRequest()
adminWsClient.permissions().addUser(new AddUserWsRequest()
.setLogin(login)
.setPermission(permission));
}

private static void deactivateUser(String login) {
adminWsClient.execute(
newPostRequest("api/users/deactivate")
adminWsClient.wsConnector().call(
new PostRequest("api/users/deactivate")
.setParam("login", login));
}

private static void removeGroupPermission(String groupName, String permission) {
adminWsClient.permissionsClient().removeGroup(new RemoveGroupWsRequest()
adminWsClient.permissions().removeGroup(new RemoveGroupWsRequest()
.setGroupName(groupName)
.setPermission(permission));
}

private static void addGroupPermission(String groupName, String permission) {
adminWsClient.permissionsClient().addGroup(new AddGroupWsRequest()
adminWsClient.permissions().addGroup(new AddGroupWsRequest()
.setGroupName(groupName)
.setPermission(permission));
}

+ 2
- 2
it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java Vedi File

@@ -229,7 +229,7 @@ public class IssuePermissionTest {
}

private void addUserPermission(String login, String projectKey, String permission) {
adminWsClient.permissionsClient().addUser(
adminWsClient.permissions().addUser(
new AddUserWsRequest()
.setLogin(login)
.setProjectKey(projectKey)
@@ -237,7 +237,7 @@ public class IssuePermissionTest {
}

private void removeGroupPermission(String groupName, String projectKey, String permission) {
adminWsClient.permissionsClient().removeGroup(new RemoveGroupWsRequest()
adminWsClient.permissions().removeGroup(new RemoveGroupWsRequest()
.setGroupName(groupName)
.setProjectKey(projectKey)
.setPermission(permission));

+ 12
- 12
it/it-tests/src/test/java/it/authorisation/PermissionTest.java Vedi File

@@ -30,6 +30,7 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.sonarqube.ws.WsPermissions;
import org.sonarqube.ws.WsPermissions.SearchTemplatesWsResponse;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.permission.AddGroupToTemplateWsRequest;
import org.sonarqube.ws.client.permission.AddGroupWsRequest;
@@ -37,14 +38,13 @@ import org.sonarqube.ws.client.permission.AddUserToTemplateWsRequest;
import org.sonarqube.ws.client.permission.AddUserWsRequest;
import org.sonarqube.ws.client.permission.CreateTemplateWsRequest;
import org.sonarqube.ws.client.permission.GroupsWsRequest;
import org.sonarqube.ws.client.permission.PermissionsWsClient;
import org.sonarqube.ws.client.permission.PermissionsService;
import org.sonarqube.ws.client.permission.RemoveGroupFromTemplateWsRequest;
import org.sonarqube.ws.client.permission.RemoveUserFromTemplateWsRequest;
import org.sonarqube.ws.client.permission.SearchTemplatesWsRequest;
import org.sonarqube.ws.client.permission.UsersWsRequest;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonarqube.ws.client.WsRequest.newPostRequest;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;

@@ -52,7 +52,7 @@ public class PermissionTest {
@ClassRule
public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
private static WsClient adminWsClient;
private static PermissionsWsClient permissionsWsClient;
private static PermissionsService permissionsWsClient;

private static final String PROJECT_KEY = "sample";
private static final String LOGIN = "george.orwell";
@@ -70,7 +70,7 @@ public class PermissionTest {
orchestrator.executeBuild(sampleProject);

adminWsClient = newAdminWsClient(orchestrator);
permissionsWsClient = adminWsClient.permissionsClient();
permissionsWsClient = adminWsClient.permissions();

createUser(LOGIN, "George Orwell");
createGroup(GROUP_NAME);
@@ -157,28 +157,28 @@ public class PermissionTest {
}

private static void createUser(String login, String name) {
adminWsClient.execute(
newPostRequest("api/users/create")
adminWsClient.wsConnector().call(
new PostRequest("api/users/create")
.setParam("login", login)
.setParam("name", name)
.setParam("password", "123456"));
}

private static void deactivateUser(String login) {
adminWsClient.execute(
newPostRequest("api/users/deactivate")
adminWsClient.wsConnector().call(
new PostRequest("api/users/deactivate")
.setParam("login", login));
}

private static void createGroup(String groupName) {
adminWsClient.execute(
newPostRequest("api/user_groups/create")
adminWsClient.wsConnector().call(
new PostRequest("api/user_groups/create")
.setParam("name", groupName));
}

private static void deleteGroup(String groupName) {
adminWsClient.execute(
newPostRequest("api/user_groups/delete")
adminWsClient.wsConnector().call(
new PostRequest("api/user_groups/delete")
.setParam("name", groupName));
}
}

+ 1
- 1
it/it-tests/src/test/java/it/issue/CommonRulesTest.java Vedi File

@@ -86,7 +86,7 @@ public class CommonRulesTest extends AbstractIssueTest {
}

private List<Issue> findIssues(String componentKey, String ruleKey) {
return adminWsClient.issuesWsClient().search(
return adminWsClient.issues().search(
new SearchWsRequest()
.setComponents(singletonList(componentKey))
.setRules(singletonList(ruleKey)))

+ 1
- 1
it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java Vedi File

@@ -147,7 +147,7 @@ public class MeasureFiltersTest {
client.userClient().create(userCreationParameters);

if (permission != null) {
adminWsClient.permissionsClient().addUser(new AddUserWsRequest()
adminWsClient.permissions().addUser(new AddUserWsRequest()
.setLogin(login)
.setPermission(permission));
}

+ 8
- 8
it/it-tests/src/test/java/util/ItUtils.java Vedi File

@@ -1,8 +1,8 @@
package util;/*
* Copyright (C) 2009-2014 SonarSource SA
* All rights reserved
* mailto:contact AT sonarsource DOT com
*/
* Copyright (C) 2009-2014 SonarSource SA
* All rights reserved
* mailto:contact AT sonarsource DOT com
*/

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@@ -32,13 +32,14 @@ import org.sonar.wsclient.issue.IssueClient;
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 static com.google.common.collect.FluentIterable.from;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.sonarqube.ws.client.HttpConnector.newHttpConnector;

public class ItUtils {

@@ -77,10 +78,9 @@ public class ItUtils {

public static WsClient newAdminWsClient(Orchestrator orchestrator) {
Server server = orchestrator.getServer();
return new WsClient(newHttpConnector()
return new HttpWsClient(new HttpConnector.Builder()
.url(server.getUrl())
.login(server.ADMIN_LOGIN)
.password(server.ADMIN_PASSWORD)
.credentials(server.ADMIN_LOGIN, server.ADMIN_PASSWORD)
.build());
}


+ 5
- 10
pom.xml Vedi File

@@ -64,6 +64,7 @@
<slf4j.version>1.7.12</slf4j.version>
<tomcat.version>8.0.18</tomcat.version>
<elasticsearch.version>1.7.2</elasticsearch.version>
<okhttp.version>2.6.0</okhttp.version>

<protobuf.version>3.0.0-beta-1</protobuf.version>
<protobuf.compiler>${settings.localRepository}/com/google/protobuf/protoc/${protobuf.version}/protoc-${protobuf.version}-${os.detected.classifier}.exe</protobuf.compiler>
@@ -1342,9 +1343,6 @@
<exists>src/main/protobuf</exists>
</file>
</activation>
<properties>
<sonar.exclusions>target/generated-sources/protobuf/**/*</sonar.exclusions>
</properties>
<build>
<plugins>
<plugin>
@@ -1352,7 +1350,7 @@
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>protobuf-compile</id>
<id>compile-protobuf-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
@@ -1389,7 +1387,7 @@
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>protobuf-compile</id>
<id>add-protobuf-generated-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
@@ -1412,9 +1410,6 @@
<exists>src/test/protobuf</exists>
</file>
</activation>
<properties>
<sonar.test.exclusions>target/generated-test-sources/protobuf/**/*</sonar.test.exclusions>
</properties>
<build>
<plugins>
<plugin>
@@ -1422,7 +1417,7 @@
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>protobuf-test-compile</id>
<id>compile-protobuf-tests</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
@@ -1459,7 +1454,7 @@
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>protobuf-test-compile</id>
<id>add-protobuf-generated-tests</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java Vedi File

@@ -24,5 +24,6 @@ public class Messages {
// constants
}

static final String NO_PERMISSION = "You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator.";
public static final String NO_PERMISSION = "You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator.";

}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java Vedi File

@@ -22,7 +22,7 @@ package org.sonar.server.permission.ws;

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

import static org.sonarqube.ws.client.permission.PermissionsWsParameters.ENDPOINT;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;

public class PermissionsWs implements WebService {
private final PermissionsWsAction[] actions;
@@ -33,7 +33,7 @@ public class PermissionsWs implements WebService {

@Override
public void define(Context context) {
NewController controller = context.createController(ENDPOINT);
NewController controller = context.createController(CONTROLLER);
controller.setDescription("Permissions management");
controller.setSince("3.7");


+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java Vedi File

@@ -22,7 +22,7 @@ package org.sonar.server.usertoken.ws;

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

import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.USER_TOKENS_ENDPOINT;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.CONTROLLER;

public class UserTokensWs implements WebService {
private final UserTokensWsAction[] actions;
@@ -33,7 +33,7 @@ public class UserTokensWs implements WebService {

@Override
public void define(Context context) {
NewController controller = context.createController(USER_TOKENS_ENDPOINT)
NewController controller = context.createController(CONTROLLER)
.setDescription("User token management. To enhance security, tokens can be used to take the place " +
"of user credentials in analysis configuration. A token can be revoked at any time.")
.setSince("5.3");

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java Vedi File

@@ -452,7 +452,7 @@ public class ProjectDataLoaderMediumTest {
loader.load(ProjectDataQuery.create().setModuleKey(project.key()));
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(ForbiddenException.class).hasMessage("You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator.");
assertThat(e).isInstanceOf(ForbiddenException.class).hasMessage(Messages.NO_PERMISSION);
}
}


+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java Vedi File

@@ -53,7 +53,7 @@ import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.permission.ws.AddGroupAction.ACTION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.ENDPOINT;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_ID;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_NAME;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
@@ -223,7 +223,7 @@ public class AddGroupActionTest {
public void fail_when_get_request() throws Exception {
expectedException.expect(ServerException.class);

ws.newGetRequest(ENDPOINT, ACTION)
ws.newGetRequest(CONTROLLER, ACTION)
.setParam(PARAM_GROUP_NAME, "sonar-administrators")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -264,7 +264,7 @@ public class AddGroupActionTest {
}

private WsTester.TestRequest newRequest() {
return ws.newPostRequest(ENDPOINT, ACTION);
return ws.newPostRequest(CONTROLLER, ACTION);
}

private void commit() {

+ 12
- 12
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java Vedi File

@@ -53,7 +53,7 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.permission.ws.AddUserAction.ACTION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.ENDPOINT;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
@@ -89,7 +89,7 @@ public class AddUserActionTest {

@Test
public void call_permission_service_with_right_data() throws Exception {
ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -105,7 +105,7 @@ public class AddUserActionTest {
dbClient.componentDao().insert(dbSession, newProjectDto("project-uuid").setKey("project-key"));
commit();

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "project-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -121,7 +121,7 @@ public class AddUserActionTest {
dbClient.componentDao().insert(dbSession, newProjectDto("project-uuid").setKey("project-key"));
commit();

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_KEY, "project-key")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -137,7 +137,7 @@ public class AddUserActionTest {
dbClient.componentDao().insert(dbSession, newView("view-uuid").setKey("view-key"));
commit();

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "view-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -152,7 +152,7 @@ public class AddUserActionTest {
public void fail_when_project_uuid_is_unknown() throws Exception {
expectedException.expect(NotFoundException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "unknown-project-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -163,7 +163,7 @@ public class AddUserActionTest {
public void fail_when_project_permission_without_project() throws Exception {
expectedException.expect(BadRequestException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN)
.execute();
@@ -175,7 +175,7 @@ public class AddUserActionTest {
insertComponent(newFileDto(newProjectDto("project-uuid"), "file-uuid"));
commit();

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "file-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -186,7 +186,7 @@ public class AddUserActionTest {
public void fail_when_get_request() throws Exception {
expectedException.expect(ServerException.class);

ws.newGetRequest(ENDPOINT, ACTION)
ws.newGetRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "george.orwell")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -196,7 +196,7 @@ public class AddUserActionTest {
public void fail_when_user_login_is_missing() throws Exception {
expectedException.expect(IllegalArgumentException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
}
@@ -205,7 +205,7 @@ public class AddUserActionTest {
public void fail_when_permission_is_missing() throws Exception {
expectedException.expect(IllegalArgumentException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "jrr.tolkien")
.execute();
}
@@ -217,7 +217,7 @@ public class AddUserActionTest {
insertComponent(newProjectDto("project-uuid").setKey("project-key"));
commit();

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "project-uuid")

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java Vedi File

@@ -54,7 +54,7 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.permission.ws.RemoveGroupAction.ACTION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.ENDPOINT;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_ID;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_NAME;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
@@ -205,7 +205,7 @@ public class RemoveGroupActionTest {
public void fail_when_get_request() throws Exception {
expectedException.expect(ServerException.class);

ws.newGetRequest(ENDPOINT, ACTION)
ws.newGetRequest(CONTROLLER, ACTION)
.setParam(PARAM_GROUP_NAME, "sonar-administrators")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -256,7 +256,7 @@ public class RemoveGroupActionTest {
}

private WsTester.TestRequest newRequest() {
return ws.newPostRequest(ENDPOINT, ACTION);
return ws.newPostRequest(CONTROLLER, ACTION);
}

private GroupDto insertGroup(String groupName) {

+ 12
- 12
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java Vedi File

@@ -53,7 +53,7 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.permission.ws.RemoveUserAction.ACTION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.ENDPOINT;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
@@ -86,7 +86,7 @@ public class RemoveUserActionTest {

@Test
public void call_permission_service_with_right_data() throws Exception {
ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -101,7 +101,7 @@ public class RemoveUserActionTest {
public void remove_with_project_uuid() throws Exception {
insertComponent(newProjectDto("project-uuid").setKey("project-key"));

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "project-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -116,7 +116,7 @@ public class RemoveUserActionTest {
public void remove_with_project_key() throws Exception {
insertComponent(newProjectDto("project-uuid").setKey("project-key"));

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_KEY, "project-key")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -131,7 +131,7 @@ public class RemoveUserActionTest {
public void remove_with_view_uuid() throws Exception {
insertComponent(newView("view-uuid").setKey("view-key"));

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "view-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -146,7 +146,7 @@ public class RemoveUserActionTest {
public void fail_when_project_does_not_exist() throws Exception {
expectedException.expect(NotFoundException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "unknown-project-uuid")
.setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN)
@@ -157,7 +157,7 @@ public class RemoveUserActionTest {
public void fail_when_project_permission_without_permission() throws Exception {
expectedException.expect(BadRequestException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN)
.execute();
@@ -168,7 +168,7 @@ public class RemoveUserActionTest {
expectedException.expect(BadRequestException.class);
insertComponent(newFileDto(newProjectDto(), "file-uuid"));

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "file-uuid")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
@@ -179,7 +179,7 @@ public class RemoveUserActionTest {
public void fail_when_get_request() throws Exception {
expectedException.expect(ServerException.class);

ws.newGetRequest(ENDPOINT, ACTION)
ws.newGetRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "george.orwell")
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
@@ -189,7 +189,7 @@ public class RemoveUserActionTest {
public void fail_when_user_login_is_missing() throws Exception {
expectedException.expect(IllegalArgumentException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.execute();
}
@@ -198,7 +198,7 @@ public class RemoveUserActionTest {
public void fail_when_permission_is_missing() throws Exception {
expectedException.expect(IllegalArgumentException.class);

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_USER_LOGIN, "jrr.tolkien")
.execute();
}
@@ -209,7 +209,7 @@ public class RemoveUserActionTest {
expectedException.expectMessage("Project id or project key can be provided, not both.");
insertComponent(newProjectDto("project-uuid").setKey("project-key"));

ws.newPostRequest(ENDPOINT, ACTION)
ws.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
.setParam(PARAM_USER_LOGIN, "ray.bradbury")
.setParam(PARAM_PROJECT_ID, "project-uuid")

+ 6
- 4
sonar-batch-protocol/pom.xml Vedi File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonarsource.sonarqube</groupId>
@@ -9,12 +10,13 @@

<artifactId>sonar-batch-protocol</artifactId>
<name>SonarQube :: Batch :: Protocol</name>
<description>Classes used for communication between batch and server</description>
<properties>
<!-- Viewer is for our internal use. This is not production code and mostly generated with Eclipse GUI builder -->
<sonar.exclusions>src/main/java/org/sonar/batch/protocol/viewer/**</sonar.exclusions>
<sonar.exclusions>target/generated-sources/**/*,src/main/java/org/sonar/batch/protocol/viewer/**</sonar.exclusions>
<sonar.test.exclusions>target/generated-test-sources/**/*</sonar.test.exclusions>
</properties>

<dependencies>

+ 0
- 5
sonar-batch/pom.xml Vedi File

@@ -100,10 +100,6 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.github.kevinsawicki</groupId>
<artifactId>http-request</artifactId>
</dependency>
<!-- For HTML Report -->
<dependency>
<groupId>org.freemarker</groupId>
@@ -118,7 +114,6 @@
<scope>test</scope>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.google.code.bean-matchers</groupId>
<artifactId>bean-matchers</artifactId>

+ 3
- 3
sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java Vedi File

@@ -21,19 +21,19 @@ package org.sonar.batch.analysis;

import org.picocontainer.injectors.ProviderAdapter;
import org.sonar.api.batch.AnalysisMode;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.cache.WSLoader;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.WsClient;

public class AnalysisWSLoaderProvider extends ProviderAdapter {
private WSLoader wsLoader;

public WSLoader provide(AnalysisProperties props, AnalysisMode mode, PersistentCache cache, ServerClient client) {
public WSLoader provide(AnalysisMode mode, PersistentCache cache, WsClient client) {
if (wsLoader == null) {
// recreate cache directory if needed for this analysis
cache.reconfigure();
wsLoader = new WSLoader(getStrategy(mode), cache, client, props);
wsLoader = new WSLoader(getStrategy(mode), cache, client);
}
return wsLoader;
}

+ 16
- 6
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java Vedi File

@@ -23,10 +23,12 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.SonarPlugin;
@@ -39,6 +41,11 @@ import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.RemotePlugin;
import org.sonar.core.platform.RemotePluginFile;
import org.sonar.home.cache.FileCache;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsResponse;

import static java.lang.String.format;

/**
* Downloads the plugins installed on server and stores them in a local user cache
@@ -52,13 +59,13 @@ public class BatchPluginInstaller implements PluginInstaller {
private final WSLoader wsLoader;
private final FileCache fileCache;
private final BatchPluginPredicate pluginPredicate;
private final ServerClient serverClient;
private final WsClient wsClient;

public BatchPluginInstaller(WSLoader wsLoader, ServerClient serverClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) {
public BatchPluginInstaller(WSLoader wsLoader, WsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) {
this.wsLoader = wsLoader;
this.fileCache = fileCache;
this.pluginPredicate = pluginPredicate;
this.serverClient = serverClient;
this.wsClient = wsClient;
}

@Override
@@ -137,14 +144,17 @@ public class BatchPluginInstaller implements PluginInstaller {

@Override
public void download(String filename, File toFile) throws IOException {
String url = "/deploy/plugins/" + key + "/" + filename;
String url = format("/deploy/plugins/%s/%s", key, filename);
if (LOG.isDebugEnabled()) {
LOG.debug("Download {} to {}", url, toFile.getAbsolutePath());
LOG.debug("Download plugin {} to {}", filename, toFile);
} else {
LOG.info("Download {}", filename);
}

serverClient.download(url, toFile);
WsResponse response = wsClient.wsConnector().call(new GetRequest(url));
try (InputStream stream = response.getContentStream()) {
FileUtils.copyInputStreamToFile(stream, toFile);
}
}
}
}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java Vedi File

@@ -91,7 +91,7 @@ public class GlobalContainer extends ComponentContainer {

CachesManager.class,
GlobalSettings.class,
ServerClient.class,
new WsClientProvider(),
DefaultServer.class,
new GlobalTempFolderProvider(),
DefaultHttpDownloader.class,

+ 0
- 168
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java Vedi File

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

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import org.sonar.core.util.DefaultHttpDownloader;

/**
* Replace the deprecated org.sonar.batch.ServerMetadata
* TODO extends Server when removing the deprecated org.sonar.batch.ServerMetadata
*
* @since 3.4
*/
@BatchSide
public class ServerClient {
private static final String GET = "GET";
private GlobalProperties props;
private DefaultHttpDownloader.BaseHttpDownloader downloader;

public ServerClient(GlobalProperties settings, EnvironmentInformation env) {
this.props = settings;
this.downloader = new DefaultHttpDownloader.BaseHttpDownloader(settings.properties(), env.toString());
}

public String getURL() {
return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
}

public URI getURI(String pathStartingWithSlash) {
Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /: " + pathStartingWithSlash);
String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
return URI.create(getURL() + path);
}

public void download(String pathStartingWithSlash, File toFile) {
download(pathStartingWithSlash, toFile, null, null);
}

public void download(String pathStartingWithSlash, File toFile, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
try {
InputStream is = load(pathStartingWithSlash, GET, false, connectTimeoutMillis, readTimeoutMillis);
Files.copy(is, toFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (HttpDownloader.HttpException he) {
throw handleHttpException(he.getUri().toString(), he.getResponseCode(), he.getResponseContent(), he);
} catch (IOException e) {
throw new IllegalStateException(String.format("Unable to download '%s' to: %s", pathStartingWithSlash, toFile), e);
}
}

public String downloadString(String pathStartingWithSlash) {
return downloadString(pathStartingWithSlash, GET, true, null);
}

public String downloadString(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer timeoutMillis) {
InputStream is = load(pathStartingWithSlash, requestMethod, wrapHttpException, null, timeoutMillis);
try {
return new String(IOUtils.toByteArray(is), "UTF-8");
} catch (IOException e) {
throw new IllegalStateException(String.format("Unable to request: %s", pathStartingWithSlash), e);
}
}

/**
* @throws IllegalStateException on I/O error, not limited to the network connection and if HTTP response code > 400 and wrapHttpException is true
* @throws HttpDownloader.HttpException if HTTP response code > 400 and wrapHttpException is false
*/
public InputStream load(String pathStartingWithSlash, String requestMethod, boolean wrapHttpException, @Nullable Integer connectTimeoutMs,
@Nullable Integer readTimeoutMs) {
URI uri = getURI(pathStartingWithSlash);

try {
if (Strings.isNullOrEmpty(getLogin())) {
return downloader.newInputSupplier(uri, requestMethod, connectTimeoutMs, readTimeoutMs).getInput();
} else {
return downloader.newInputSupplier(uri, requestMethod, getLogin(), getPassword(), connectTimeoutMs, readTimeoutMs).getInput();
}
} catch (HttpDownloader.HttpException he) {
if (wrapHttpException) {
throw handleHttpException(he.getUri().toString(), he.getResponseCode(), he.getResponseContent(), he);
} else {
throw he;
}
} catch (IOException e) {
throw new IllegalStateException(String.format("Unable to request: %s", uri), e);
}
}

public RuntimeException handleHttpException(String url, int responseCode, String responseContent, Exception he) {
if (responseCode == 401) {
return MessageException.of(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD), he);
}
if (responseCode == 403) {
// SONAR-4397 Details are in response content
return MessageException.of(tryParseAsJsonError(responseContent), he);
}
return MessageException.of(String.format("Fail to execute request [code=%s, url=%s]: %s", responseCode, url, responseContent), he);
}

private static String tryParseAsJsonError(String responseContent) {
try {
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(responseContent).getAsJsonObject();
JsonArray errors = obj.getAsJsonArray("errors");
List<String> errorMessages = new ArrayList<>();
for (JsonElement e : errors) {
errorMessages.add(e.getAsJsonObject().get("msg").getAsString());
}
return Joiner.on(", ").join(errorMessages);
} catch (Exception e) {
return responseContent;
}
}

public String getMessageWhenNotAuthorized() {
if (Strings.isNullOrEmpty(getLogin()) && Strings.isNullOrEmpty(getPassword())) {
return "Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties %s and %s.";
}
return "Not authorized. Please check the properties %s and %s.";
}

public String getLogin() {
return props.property(CoreProperties.LOGIN);
}

public String getPassword() {
return props.property(CoreProperties.PASSWORD);
}
}

+ 90
- 0
sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java Vedi File

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

import com.google.common.base.Joiner;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;

import static java.lang.String.format;

public class WsClientLoggingInterceptor implements Interceptor {

private static final Logger LOG = Loggers.get(WsClientLoggingInterceptor.class);

@Override
public Response intercept(Chain chain) throws IOException {
Response response = logAndSendRequest(chain);
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
if (StringUtils.isBlank(response.request().header("Authorization"))) {
// not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048
throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " +
"Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD));
}
// credentials are not valid
throw MessageException.of(format("Not authorized. Please check the properties %s and %s.",
CoreProperties.LOGIN, CoreProperties.PASSWORD));
}
if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
// SONAR-4397 Details are in response content
throw MessageException.of(tryParseAsJsonError(response.body().string()));
}
return response;
}

private Response logAndSendRequest(Chain chain) throws IOException {
Request request = chain.request();
Response response;
Profiler profiler = Profiler.createIfDebug(LOG).startTrace(format("%s %s", request.method(), request.url()));
response = chain.proceed(request);
profiler.stopDebug(format("%s %d %s", request.method(), response.code(), request.url()));
return response;
}

private static String tryParseAsJsonError(String responseContent) {
try {
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(responseContent).getAsJsonObject();
JsonArray errors = obj.getAsJsonArray("errors");
List<String> errorMessages = new ArrayList<>();
for (JsonElement e : errors) {
errorMessages.add(e.getAsJsonObject().get("msg").getAsString());
}
return Joiner.on(", ").join(errorMessages);
} catch (Exception e) {
return responseContent;
}
}
}

+ 63
- 0
sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java Vedi File

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

import org.picocontainer.injectors.ProviderAdapter;
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.WsClient;

import static java.lang.Integer.parseInt;
import static java.lang.String.valueOf;
import static org.apache.commons.lang.StringUtils.defaultIfBlank;

@BatchSide
public class WsClientProvider extends ProviderAdapter {

static final int CONNECT_TIMEOUT_MS = 5_000;
static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout";
static final int DEFAULT_READ_TIMEOUT_SEC = 60;

private HttpWsClient wsClient;

public synchronized WsClient 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();

// TODO proxy

String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC));
builder
.readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000)
.connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS)
.userAgent(env.toString())
.url(url)
.credentials(settings.property(CoreProperties.LOGIN), settings.property(CoreProperties.PASSWORD))
.interceptor(new WsClientLoggingInterceptor());

wsClient = new HttpWsClient(builder.build());
}
return wsClient;
}
}

+ 3
- 4
sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java Vedi File

@@ -20,10 +20,9 @@
package org.sonar.batch.cache;

import org.picocontainer.injectors.ProviderAdapter;
import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.WsClient;

public class StrategyWSLoaderProvider extends ProviderAdapter {
private final LoadStrategy strategy;
@@ -33,9 +32,9 @@ public class StrategyWSLoaderProvider extends ProviderAdapter {
this.strategy = strategy;
}

public WSLoader provide(PersistentCache cache, ServerClient client, GlobalProperties globalProps) {
public WSLoader provide(PersistentCache cache, WsClient client) {
if (wsLoader == null) {
wsLoader = new WSLoader(strategy, cache, client, globalProps);
wsLoader = new WSLoader(strategy, cache, client);
}
return wsLoader;
}

+ 26
- 33
sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java Vedi File

@@ -21,56 +21,53 @@ package org.sonar.batch.cache;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.sonar.api.utils.HttpDownloader.HttpException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.bootstrap.UserProperties;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.WsClient;

import static org.sonar.batch.cache.WSLoader.ServerStatus.ACCESSIBLE;
import static org.sonar.batch.cache.WSLoader.ServerStatus.NOT_ACCESSIBLE;
import static org.sonar.batch.cache.WSLoader.ServerStatus.UNKNOWN;

public class WSLoader {
static final String SONAR_WS_TIMEOUT_PROPS = "sonar.ws.timeout";
private static final Logger LOG = Loggers.get(WSLoader.class);
private static final String FAIL_MSG = "Server is not accessible and data is not cached";
private static final int CONNECT_TIMEOUT = 5_000;
private static final int DEFAULT_READ_TIMEOUT = 60_000;
private static final String REQUEST_METHOD = "GET";

public enum ServerStatus {
UNKNOWN, ACCESSIBLE, NOT_ACCESSIBLE;
UNKNOWN, ACCESSIBLE, NOT_ACCESSIBLE
}

public enum LoadStrategy {
SERVER_FIRST, CACHE_FIRST, SERVER_ONLY, CACHE_ONLY;
SERVER_FIRST, CACHE_FIRST, SERVER_ONLY, CACHE_ONLY
}

private final LoadStrategy defautLoadStrategy;
private final ServerClient client;
private final WsClient client;
private final PersistentCache cache;
private final UserProperties userProperties;
private ServerStatus serverStatus;

private DataLoader<String> stringServerLoader = new DataLoader<String>() {
@Override
public String load(String id) throws IOException {
InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, getReadTimeout());
String str = IOUtils.toString(is, StandardCharsets.UTF_8);
try {
cache.put(id, str.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new IllegalStateException("Error saving to WS cache", e);
GetRequest getRequest = new GetRequest(id);
try (Reader reader = client.wsConnector().call(getRequest).getContentReader()) {
String str = IOUtils.toString(reader);
try {
cache.put(id, str.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new IllegalStateException("Error saving to WS cache", e);
}
return str;
}
return str;
}

};

private DataLoader<String> stringCacheLoader = new DataLoader<String>() {
@@ -83,13 +80,14 @@ public class WSLoader {
private DataLoader<InputStream> streamServerLoader = new DataLoader<InputStream>() {
@Override
public InputStream load(String id) throws IOException {
InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, getReadTimeout());
try {
cache.put(id, is);
} catch (IOException e) {
throw new IllegalStateException("Error saving to WS cache", e);
GetRequest getRequest = new GetRequest(id);
try (InputStream is = client.wsConnector().call(getRequest).getContentStream()) {
try {
cache.put(id, is);
} catch (IOException e) {
throw new IllegalStateException("Error saving to WS cache", e);
}
}
is.close();
return cache.getStream(id);
}
};
@@ -101,7 +99,7 @@ public class WSLoader {
}
};

private class NotAvailableException extends Exception {
private static class NotAvailableException extends Exception {
private static final long serialVersionUID = 1L;

public NotAvailableException(String message) {
@@ -113,18 +111,13 @@ public class WSLoader {
}
}

public WSLoader(LoadStrategy strategy, PersistentCache cache, ServerClient client, UserProperties settings) {
public WSLoader(LoadStrategy strategy, PersistentCache cache, WsClient client) {
this.defautLoadStrategy = strategy;
this.userProperties = settings;
this.serverStatus = UNKNOWN;
this.cache = cache;
this.client = client;
}

private int getReadTimeout() {
return userProperties.properties().containsKey(SONAR_WS_TIMEOUT_PROPS) ? (Integer.parseInt(userProperties.property(SONAR_WS_TIMEOUT_PROPS)) * 1000) : DEFAULT_READ_TIMEOUT;
}

@Nonnull
public WSLoaderResult<InputStream> loadStream(String id) {
return load(id, defautLoadStrategy, streamServerLoader, streamCacheLoader);
@@ -200,7 +193,7 @@ public class WSLoader {
throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause());
}
}
throw new IllegalStateException("Server is not available", serverNotAvailable.getCause());
throw new IllegalStateException("Server is not available: " + client.wsConnector().baseUrl(), serverNotAvailable.getCause());
}
}


+ 80
- 136
sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java Vedi File

@@ -19,71 +19,63 @@
*/
package org.sonar.batch.report;

import com.github.kevinsawicki.http.HttpRequest;
import com.google.common.annotations.VisibleForTesting;
import com.google.gson.Gson;
import java.io.BufferedWriter;
import com.google.common.io.Files;
import com.squareup.okhttp.HttpUrl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import javax.annotation.CheckForNull;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.ZipUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.batch.analysis.DefaultAnalysisMode;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.protocol.output.BatchReportWriter;
import org.sonar.batch.scan.ImmutableProjectReactor;
import org.sonar.batch.util.BatchUtils;
import org.sonarqube.ws.WsCe;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.ce.SubmitWsRequest;

import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.defaultIfBlank;

@BatchSide
public class ReportPublisher implements Startable {

private static final Logger LOG = LoggerFactory.getLogger(ReportPublisher.class);
private static final Logger LOG = Loggers.get(ReportPublisher.class);

public static final String KEEP_REPORT_PROP_KEY = "sonar.batch.keepReport";
public static final String VERBOSE_KEY = "sonar.verbose";
public static final String DUMP_REPORT_PROP_KEY = "sonar.batch.dumpReportDir";
public static final String JSON_DETAILS_FILE = "analysis-details.json";
public static final String METADATA_DUMP_FILENAME = "analysis-details.json";

private final ServerClient serverClient;
private final Server server;
private final Settings settings;
private final WsClient wsClient;
private final AnalysisContextReportPublisher contextPublisher;
private final ImmutableProjectReactor projectReactor;
private final DefaultAnalysisMode analysisMode;
private final TempFolder temp;
private final AnalysisContextReportPublisher contextPublisher;

private ReportPublisherStep[] publishers;
private final ReportPublisherStep[] publishers;

private File reportDir;
private BatchReportWriter writer;

public ReportPublisher(Settings settings, ServerClient serverClient, Server server, AnalysisContextReportPublisher contextPublisher,
public ReportPublisher(Settings settings, WsClient wsClient, AnalysisContextReportPublisher contextPublisher,
ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
this.serverClient = serverClient;
this.server = server;
this.settings = settings;
this.wsClient = wsClient;
this.contextPublisher = contextPublisher;
this.projectReactor = projectReactor;
this.settings = settings;
this.analysisMode = analysisMode;
this.temp = temp;
this.publishers = publishers;
@@ -101,7 +93,7 @@ public class ReportPublisher implements Startable {
if (!settings.getBoolean(KEEP_REPORT_PROP_KEY) && !settings.getBoolean(VERBOSE_KEY)) {
FileUtils.deleteQuietly(reportDir);
} else {
LOG.info("Batch report generated in " + reportDir);
LOG.info("Analysis report generated in " + reportDir);
}
}

@@ -117,22 +109,22 @@ public class ReportPublisher implements Startable {
// If this is a issues mode analysis then we should not upload reports
String taskId = null;
if (!analysisMode.isIssues()) {
File report = prepareReport();
File report = generateReportFile();
if (!analysisMode.isMediumTest()) {
taskId = sendOrDumpReport(report);
taskId = upload(report);
}
}
logSuccess(LoggerFactory.getLogger(getClass()), taskId);
logSuccess(taskId);
}

private File prepareReport() {
private File generateReportFile() {
try {
long startTime = System.currentTimeMillis();
for (ReportPublisherStep publisher : publishers) {
publisher.publish(writer);
}
long stopTime = System.currentTimeMillis();
LOG.info("Analysis reports generated in " + (stopTime - startTime) + "ms, dir size=" + FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir)));
LOG.info("Analysis report generated in " + (stopTime - startTime) + "ms, dir size=" + FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir)));

startTime = System.currentTimeMillis();
File reportZip = temp.newFile("batch-report", ".zip");
@@ -141,129 +133,81 @@ public class ReportPublisher implements Startable {
LOG.info("Analysis reports compressed in " + (stopTime - startTime) + "ms, zip size=" + FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(reportZip)));
return reportZip;
} catch (IOException e) {
throw new IllegalStateException("Unable to prepare batch report", e);
throw new IllegalStateException("Unable to prepare analysis report", e);
}
}

@CheckForNull
/**
* Uploads the report file to server and returns the generated task id
*/
@VisibleForTesting
String sendOrDumpReport(File report) {
ProjectDefinition projectDefinition = projectReactor.getRoot();
String effectiveKey = projectDefinition.getKeyWithBranch();
String relativeUrl = String.format("/api/ce/submit?projectKey=%s&projectName=%s&projectBranch=%s",
projectDefinition.getKey(), BatchUtils.encodeForUrl(projectDefinition.getName()), BatchUtils.encodeForUrl(projectDefinition.getBranch()));

String dumpDirLocation = settings.getString(DUMP_REPORT_PROP_KEY);
if (dumpDirLocation == null) {
return uploadMultiPartReport(report, relativeUrl);
} else {
dumpReport(dumpDirLocation, effectiveKey, relativeUrl, report);
return null;
}
}

private String uploadMultiPartReport(File report, String relativeUrl) {
LOG.debug("Publish results");
String upload(File report) {
LOG.debug("Upload report");
long startTime = System.currentTimeMillis();
URL url;
try {
url = new URL(serverClient.getURL() + relativeUrl);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL", e);
}
HttpRequest request = HttpRequest.post(url);
request.trustAllCerts();
request.trustAllHosts();
request.header("User-Agent", format("SonarQube %s", server.getVersion()));
request.basic(serverClient.getLogin(), serverClient.getPassword());
request.part("report", null, "application/octet-stream", report);
if (!request.ok()) {
throw serverClient.handleHttpException(url.toString(), request.code(), request.body(), null);
}
ProjectDefinition projectDefinition = projectReactor.getRoot();
SubmitWsRequest submitRequest = new SubmitWsRequest();
submitRequest.setProjectKey(projectDefinition.getKey());
submitRequest.setProjectName(projectDefinition.getName());
submitRequest.setProjectBranch(projectDefinition.getBranch());
submitRequest.setReport(report);
WsCe.SubmitResponse submitResponse = wsClient.computeEngine().submit(submitRequest);
long stopTime = System.currentTimeMillis();
LOG.info("Analysis reports sent to server in " + (stopTime - startTime) + "ms");
String responseStr = request.body();
SubmitResponse response = new Gson().fromJson(responseStr, SubmitResponse.class);
return response.getTaskId();
}

private static class SubmitResponse {
private String taskId;

public String getTaskId() {
return taskId;
}

public void setTaskId(String taskId) {
this.taskId = taskId;
}
}

private static void dumpReport(String dumpDirLocation, String projectKey, String relativeUrl, File report) {
LOG.debug("Dump report to file");
try {
dumpReportImpl(dumpDirLocation, projectKey, relativeUrl, report);
} catch (IOException | URISyntaxException e) {
LOG.error("Failed to dump report to directory " + dumpDirLocation, e);
}
}

private static void dumpReportImpl(String dumpDirLocation, String projectKey, String relativeUrl, File report) throws IOException, URISyntaxException {
File dumpDir = new File(dumpDirLocation);
if (!dumpDir.exists() || !dumpDir.isDirectory()) {
LOG.warn("Report dump directory '{}' does not exist or is not a directory", dumpDirLocation);
return;
}
long dateTime = new Date().getTime();
File dumpedZip = new File(dumpDir, format("batch-report_%s_%s.zip", projectKey, dateTime));
FileUtils.copyFile(report, new FileOutputStream(dumpedZip));
File dumpedMetadata = new File(dumpDir, format("batch-report_%s_%s.txt", projectKey, dateTime));
FileUtils.write(dumpedMetadata, relativeUrl);
LOG.info("Batch report dumped to {}", dumpedZip.getAbsolutePath());
LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
return submitResponse.getTaskId();
}

@VisibleForTesting
void logSuccess(Logger logger, @Nullable String taskId) {
if (analysisMode.isIssues() || analysisMode.isMediumTest()) {
logger.info("ANALYSIS SUCCESSFUL");

void logSuccess(@Nullable String taskId) {
if (taskId == null) {
LOG.info("ANALYSIS SUCCESSFUL");
} else {
String baseUrl = settings.getString(CoreProperties.SERVER_BASE_URL);
if (baseUrl.equals(settings.getDefaultValue(CoreProperties.SERVER_BASE_URL))) {
// If server base URL was not configured in Sonar server then is is better to take URL configured on batch side
baseUrl = serverClient.getURL();
}
if (!baseUrl.endsWith("/")) {
baseUrl += "/";
}
Map<String, String> metadata = new LinkedHashMap<>();
String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
String url = baseUrl + "dashboard/index/" + effectiveKey;
logger.info("ANALYSIS SUCCESSFUL, you can browse {}", url);
logger.info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report.");
String taskUrl = null;
if (taskId != null) {
taskUrl = baseUrl + "api/ce/task?id=" + taskId;
logger.info("More about the report processing at {}", taskUrl);
}

writeJson(url, taskUrl);
metadata.put("projectKey", effectiveKey);

URL dashboardUrl = HttpUrl.parse(publicUrl()).newBuilder()
.addPathSegment("dashboard").addPathSegment("index").addPathSegment(effectiveKey)
.build()
.url();
metadata.put("dashboardUrl", dashboardUrl.toExternalForm());

URL taskUrl = HttpUrl.parse(publicUrl()).newBuilder()
.addPathSegment("api").addPathSegment("ce").addPathSegment("task")
.addQueryParameter("id", taskId)
.build()
.url();
metadata.put("ceTaskId", taskId);
metadata.put("ceTaskUrl", taskUrl.toExternalForm());

LOG.info("ANALYSIS SUCCESSFUL, you can browse {}", dashboardUrl);
LOG.info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report");
LOG.info("More about the report processing at {}", taskUrl);

dumpMetadata(metadata);
}
}

private void writeJson(String dashboardUrl, @Nullable String taskUrl) {
File exportFile = new File(projectReactor.getRoot().getWorkDir(), JSON_DETAILS_FILE);
try (Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(exportFile), StandardCharsets.UTF_8))) {
private void dumpMetadata(Map<String, String> metadata) {
File file = new File(projectReactor.getRoot().getWorkDir(), METADATA_DUMP_FILENAME);
try (Writer output = Files.newWriter(file, StandardCharsets.UTF_8)) {
JsonWriter json = JsonWriter.of(output);
json.beginObject();
json.prop("dashboardUrl", dashboardUrl);
if (taskUrl != null) {
json.prop("ceTaskUrl", taskUrl);
for (Map.Entry<String, String> entry : metadata.entrySet()) {
json.prop(entry.getKey(), entry.getValue());
}
json.endObject();

LOG.debug("Analysis URLs written to {}", exportFile);
LOG.debug("Report metadata written to {}", file);
} catch (IOException e) {
throw new IllegalStateException("Unable to write analysis URLs in file " + exportFile.getAbsolutePath(), e);
throw new IllegalStateException("Unable to dump " + file, e);
}
}

/**
* The public URL is optionally configured on server. If not, then the regular URL is returned.
* See https://jira.sonarsource.com/browse/SONAR-4239
*/
private String publicUrl() {
return defaultIfBlank(settings.getString(CoreProperties.SERVER_BASE_URL), wsClient.wsConnector().baseUrl());
}
}

+ 3
- 2
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java Vedi File

@@ -21,12 +21,12 @@ package org.sonar.batch.repository;

import com.google.common.base.Throwables;

import org.sonar.api.utils.HttpDownloader.HttpException;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Date;
import java.util.Map;

@@ -42,6 +42,7 @@ import org.sonar.batch.util.BatchUtils;
import org.sonarqube.ws.WsBatch.WsProjectResponse;
import org.sonarqube.ws.WsBatch.WsProjectResponse.FileDataByPath;
import org.sonarqube.ws.WsBatch.WsProjectResponse.Settings;
import org.sonarqube.ws.client.HttpException;

public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoader {
private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectRepositoriesLoader.class);
@@ -85,7 +86,7 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad
for (Throwable t : Throwables.getCausalChain(e)) {
if (t instanceof HttpException) {
HttpException http = (HttpException) t;
return http.getResponseCode() != 404;
return http.code() != HttpURLConnection.HTTP_NOT_FOUND;
}
}


+ 4
- 8
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java Vedi File

@@ -32,9 +32,10 @@ import javax.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;

import static com.google.common.base.Preconditions.checkState;

public class DefaultQualityProfileLoader implements QualityProfileLoader {
private static final String WS_URL = "/api/qualityprofiles/search.protobuf";

@@ -62,12 +63,6 @@ public class DefaultQualityProfileLoader implements QualityProfileLoader {
return loadResource(url, fromCache);
}

private static void validate(Collection<QualityProfile> profiles) {
if (profiles == null || profiles.isEmpty()) {
throw new IllegalStateException("No quality profiles has been found this project, you probably don't have any language plugin suitable for this analysis.");
}
}

private List<QualityProfile> loadResource(String url, @Nullable MutableBoolean fromCache) {
WSLoaderResult<InputStream> result = wsLoader.loadStream(url);
if (fromCache != null) {
@@ -85,7 +80,8 @@ public class DefaultQualityProfileLoader implements QualityProfileLoader {
}

List<QualityProfile> profilesList = profiles.getProfilesList();
validate(profilesList);
checkState(profilesList != null && !profilesList.isEmpty(),
"No quality profiles has been found this project, you probably don't have any language plugin suitable for this analysis.");
return profilesList;
}


+ 3
- 10
sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java Vedi File

@@ -19,17 +19,15 @@
*/
package org.sonar.batch.analysis;

import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.sonar.api.batch.AnalysisMode;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.cache.WSLoader;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.WsClient;

import static org.assertj.core.api.Assertions.assertThat;

@@ -38,27 +36,22 @@ public class AnalysisWSLoaderProviderTest {
private PersistentCache cache;

@Mock
private ServerClient client;
private WsClient client;

@Mock
private AnalysisMode mode;

private AnalysisWSLoaderProvider loaderProvider;
private Map<String, String> propMap;
private AnalysisProperties props;

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
loaderProvider = new AnalysisWSLoaderProvider();
propMap = new HashMap<>();
}

@Test
public void testDefault() {
props = new AnalysisProperties(propMap, null);

WSLoader loader = loaderProvider.provide(props, mode, cache, client);
WSLoader loader = loaderProvider.provide(mode, cache, client);
assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY);
}
}

+ 10
- 11
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java Vedi File

@@ -19,18 +19,17 @@
*/
package org.sonar.batch.bootstrap;

import org.sonar.batch.cache.WSLoaderResult;

import org.sonar.batch.cache.WSLoader;
import java.io.File;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.batch.cache.WSLoader;
import org.sonar.batch.cache.WSLoaderResult;
import org.sonar.core.platform.RemotePlugin;
import org.sonar.home.cache.FileCache;

import java.io.File;
import java.util.List;
import org.sonarqube.ws.client.WsClient;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
@@ -48,15 +47,15 @@ public class BatchPluginInstallerTest {
public ExpectedException thrown = ExpectedException.none();

FileCache fileCache = mock(FileCache.class);
ServerClient serverClient = mock(ServerClient.class);
WsClient wsClient = mock(WsClient.class);
BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class);

@Test
public void listRemotePlugins() {

WSLoader wsLoader = mock(WSLoader.class);
when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn(new WSLoaderResult<String>("checkstyle\nsqale", true));
BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, serverClient, fileCache, pluginPredicate);
when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn(new WSLoaderResult<>("checkstyle\nsqale", true));
BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);

List<RemotePlugin> remotePlugins = installer.listRemotePlugins();
assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale");
@@ -68,7 +67,7 @@ public class BatchPluginInstallerTest {
when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar);

WSLoader wsLoader = mock(WSLoader.class);
BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, serverClient, fileCache, pluginPredicate);
BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);

RemotePlugin remote = new RemotePlugin("checkstyle").setFile("checkstyle-plugin.jar", "fakemd5_1");
File file = installer.download(remote);
@@ -83,6 +82,6 @@ public class BatchPluginInstallerTest {
WSLoader wsLoader = mock(WSLoader.class);
doThrow(new IllegalStateException()).when(wsLoader).loadString("/deploy/plugins/index.txt");

new BatchPluginInstaller(wsLoader, serverClient, fileCache, pluginPredicate).installRemotes();
new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate).installRemotes();
}
}

+ 0
- 138
sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java Vedi File

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

import org.sonar.batch.util.BatchUtils;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ServerClientTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();

private MockHttpServer server = null;
private GlobalProperties bootstrapProps = mock(GlobalProperties.class);

@After
public void stopServer() {
if (server != null) {
server.stop();
}
}

@Test
public void should_remove_url_ending_slash() {
GlobalProperties settings = mock(GlobalProperties.class);
when(settings.property("sonar.host.url")).thenReturn("http://localhost:8080/sonar/");
ServerClient client = new ServerClient(settings, new EnvironmentInformation("Junit", "4"));

assertThat(client.getURL()).isEqualTo("http://localhost:8080/sonar");
}

@Test
public void should_request_url() throws Exception {
startServer(null, "this is the content");
assertThat(newServerClient().downloadString("/foo")).isEqualTo("this is the content");
}

@Test
public void should_escape_html_from_url() throws Exception {
startServer(null, "this is the content");
assertThat(newServerClient().downloadString("/<foo>")).isEqualTo("this is the content");
}

@Test
public void should_download_file() throws Exception {
startServer(null, "this is the content");
File file = temp.newFile();
newServerClient().download("/foo", file);
assertThat(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)).isEqualTo("this is the content");
}

@Test
public void should_fail_if_unauthorized_with_no_login_password() throws Exception {
startServer(401, null);
thrown.expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password.");
newServerClient().downloadString("/foo");
}

@Test
public void should_fail_if_unauthorized_with_login_password_provided() throws Exception {
startServer(401, null);

when(bootstrapProps.property(eq("sonar.login"))).thenReturn("login");
when(bootstrapProps.property(eq("sonar.password"))).thenReturn("password");

thrown.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password");
newServerClient().downloadString("/foo");
}

@Test
public void should_display_json_error_when_403() throws Exception {
startServer(403, "{\"errors\":[{\"msg\":\"Insufficient privileges\"}]}");
thrown.expectMessage("Insufficient privileges");
newServerClient().downloadString("/foo");
}

@Test
public void should_fail_if_error() throws Exception {
startServer(500, null);
thrown.expectMessage("Fail to execute request [code=500, url=http://localhost:" + server.getPort() + "/foo]");
newServerClient().downloadString("/foo");
}

@Test
public void string_encode() {
assertThat(BatchUtils.encodeForUrl("my value")).isEqualTo("my+value");
}

private ServerClient newServerClient() {
when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort());
return new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4"));
}

private void startServer(Integer responseStatus, String responseData) throws Exception {
server = new MockHttpServer();
server.start();
if (responseStatus != null) {
server.setMockResponseStatus(responseStatus);
}
if (responseData != null) {
server.setMockResponseData(responseData);
}
}
}

+ 129
- 0
sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java Vedi File

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

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;

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

public class WsClientLoggingInterceptorTest {

@Rule
public LogTester logTester = new LogTester();

@Rule
public ExpectedException expectedException = ExpectedException.none();

WsClientLoggingInterceptor underTest = new WsClientLoggingInterceptor();
Interceptor.Chain chain = mock(Interceptor.Chain.class);

@Test
public void log_and_profile_request_if_debug_level() throws Exception {
Request request = newRequest();
Response response = newResponse(request, 200, "");
when(chain.request()).thenReturn(request);
when(chain.proceed(request)).thenReturn(response);

logTester.setLevel(LoggerLevel.DEBUG);
Response result = underTest.intercept(chain);

// do not fail the execution -> interceptor returns the response
assertThat(result).isSameAs(response);

// check logs
List<String> debugLogs = logTester.logs(LoggerLevel.DEBUG);
assertThat(debugLogs).hasSize(1);
assertThat(debugLogs.get(0)).contains("GET 200 https://localhost:9000/api/issues/search | time=");
List<String> traceLogs = logTester.logs(LoggerLevel.TRACE);
assertThat(traceLogs).hasSize(1);
assertThat(traceLogs.get(0)).isEqualTo("GET https://localhost:9000/api/issues/search");
}

@Test
public void fail_if_requires_authentication() throws Exception {
expectedException.expect(MessageException.class);
expectedException
.expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password.");

Request request = newRequest();
Response response = newResponse(request, 401, "");
when(chain.request()).thenReturn(request);
when(chain.proceed(request)).thenReturn(response);

underTest.intercept(chain);
}

@Test
public void fail_if_credentials_are_not_valid() throws Exception {
expectedException.expect(MessageException.class);
expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password.");

Request request = new Request.Builder()
.url("https://localhost:9000/api/issues/search")
.header("Authorization", "Basic BAD_CREDENTIALS")
.get()
.build();
Response response = newResponse(request, 401, "");
when(chain.request()).thenReturn(request);
when(chain.proceed(request)).thenReturn(response);

underTest.intercept(chain);
}

@Test
public void fail_if_requires_permission() throws Exception {
expectedException.expect(MessageException.class);
expectedException.expectMessage("missing scan permission, missing another permission");

Request request = newRequest();
Response response = newResponse(request, 403, "{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}");
when(chain.request()).thenReturn(request);
when(chain.proceed(request)).thenReturn(response);

underTest.intercept(chain);
}

private Request newRequest() {
return new Request.Builder().url("https://localhost:9000/api/issues/search").get().build();
}

private Response newResponse(Request getRequest, int code, String jsonBody) {
return new Response.Builder().request(getRequest)
.code(code)
.protocol(Protocol.HTTP_1_1)
.body(ResponseBody.create(MediaType.parse("application/json"), jsonBody))
.build();
}

}

+ 81
- 0
sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java Vedi File

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

import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import org.sonarqube.ws.client.HttpConnector;
import org.sonarqube.ws.client.WsClient;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.batch.bootstrap.WsClientProvider.CONNECT_TIMEOUT_MS;
import static org.sonar.batch.bootstrap.WsClientProvider.DEFAULT_READ_TIMEOUT_SEC;

public class WsClientProviderTest {

WsClientProvider underTest = new WsClientProvider();
EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");

@Test
public void provide_client_with_default_settings() {
GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());

WsClient client = underTest.provide(settings, env);

assertThat(client).isNotNull();
HttpConnector httpConnector = (HttpConnector) client.wsConnector();
assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/");
assertThat(httpConnector.okHttpClient().getProxy()).isNull();
assertThat(httpConnector.okHttpClient().getConnectTimeout()).isEqualTo(5_000);
assertThat(httpConnector.okHttpClient().getReadTimeout()).isEqualTo(60_000);
assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
assertThat(httpConnector.okHttpClient().interceptors())
.hasSize(1)
.hasOnlyElementsOfType(WsClientLoggingInterceptor.class);
}

@Test
public void provide_client_with_custom_settings() {
Map<String, String> props = new HashMap<>();
props.put("sonar.host.url", "https://here/sonarqube");
props.put("sonar.login", "theLogin");
props.put("sonar.password", "thePassword");
props.put("sonar.ws.timeout", "42");
GlobalProperties settings = new GlobalProperties(props);

WsClient client = underTest.provide(settings, env);

assertThat(client).isNotNull();
HttpConnector httpConnector = (HttpConnector) client.wsConnector();
assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
assertThat(httpConnector.okHttpClient().getProxy()).isNull();
assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
}

@Test
public void build_singleton() {
GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
WsClient first = underTest.provide(settings, env);
WsClient second = underTest.provide(settings, env);
assertThat(first).isSameAs(second);
}
}

+ 8
- 13
sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java Vedi File

@@ -19,24 +19,22 @@
*/
package org.sonar.batch.cache;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.io.Files;
import org.junit.rules.ExpectedException;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.Rule;
import org.junit.Before;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.home.cache.PersistentCache;

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

public class DefaultProjectCacheStatusTest {
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
@@ -45,13 +43,10 @@ public class DefaultProjectCacheStatusTest {
public ExpectedException exception = ExpectedException.none();

ProjectCacheStatus cacheStatus;
PersistentCache cache;
ServerClient client;
PersistentCache cache = mock(PersistentCache.class);

@Before
public void setUp() {
client = mock(ServerClient.class);
cache = mock(PersistentCache.class);
when(cache.getDirectory()).thenReturn(tmp.getRoot().toPath());
cacheStatus = new DefaultProjectCacheStatus(cache);
}

+ 6
- 8
sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java Vedi File

@@ -19,22 +19,20 @@
*/
package org.sonar.batch.cache;

import org.sonar.batch.protocol.input.ProjectRepositories;
import java.util.HashMap;
import org.junit.Test;
import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.protocol.input.ProjectRepositories;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.home.cache.PersistentCache;

import java.util.HashMap;
import org.sonarqube.ws.client.WsClient;

import static org.mockito.Mockito.mock;
import org.sonar.core.platform.ComponentContainer;
import org.junit.Test;

public class ProjectSyncContainerTest {
private ComponentContainer createParentContainer() {
PersistentCache cache = mock(PersistentCache.class);
ServerClient server = mock(ServerClient.class);
WsClient server = mock(WsClient.class);

GlobalProperties globalProps = new GlobalProperties(new HashMap<String, String>());
ComponentContainer parent = new ComponentContainer();

+ 5
- 10
sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java Vedi File

@@ -23,33 +23,28 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.WsClient;

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

public class StrategyWSLoaderProviderTest {
@Mock
private PersistentCache cache;

@Mock
private ServerClient client;

private GlobalProperties globalProps;
private WsClient client;

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
globalProps = mock(GlobalProperties.class);
}

@Test
public void testStrategy() {
StrategyWSLoaderProvider provider = new StrategyWSLoaderProvider(LoadStrategy.CACHE_FIRST);
WSLoader wsLoader = provider.provide(cache, client, globalProps);
WSLoader wsLoader = provider.provide(cache, client);

assertThat(wsLoader.getDefaultStrategy()).isEqualTo(LoadStrategy.CACHE_FIRST);
}
@@ -57,8 +52,8 @@ public class StrategyWSLoaderProviderTest {
@Test
public void testSingleton() {
StrategyWSLoaderProvider provider = new StrategyWSLoaderProvider(LoadStrategy.CACHE_FIRST);
WSLoader wsLoader = provider.provide(cache, client, globalProps);
WSLoader wsLoader = provider.provide(cache, client);

assertThat(provider.provide(null, null, null)).isEqualTo(wsLoader);
assertThat(provider.provide(null, null)).isEqualTo(wsLoader);
}
}

+ 60
- 103
sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java Vedi File

@@ -19,34 +19,28 @@
*/
package org.sonar.batch.cache;

import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.bootstrap.UserProperties;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.PersistentCache;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.MockWsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsConnector;
import org.sonarqube.ws.client.WsRequest;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -58,35 +52,20 @@ public class WSLoaderTest {
private final static String cacheValue = "cache";
private final static String serverValue = "server";

@Mock
private ServerClient client;
@Mock
private PersistentCache cache;
@Rule
public ExpectedException exception = ExpectedException.none();
private UserProperties props;

@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenReturn(IOUtils.toInputStream(serverValue));
when(cache.getString(ID)).thenReturn(cacheValue);
when(client.getURI(anyString())).thenAnswer(new Answer<URI>() {
@Override
public URI answer(InvocationOnMock invocation) throws Throwable {
return new URI((String) invocation.getArguments()[0]);
}
});
props = mock(UserProperties.class);
}
WsClient ws = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS);
PersistentCache cache = mock(PersistentCache.class);

@Test
public void dont_retry_server_offline() throws IOException {
turnServerOffline();
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
when(cache.getString(ID)).thenReturn(cacheValue);
WSLoader underTest = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);

assertResult(loader.loadString(ID), cacheValue, true);
assertResult(loader.loadString(ID), cacheValue, true);
assertResult(underTest.loadString(ID), cacheValue, true);
assertResult(underTest.loadString(ID), cacheValue, true);

assertUsedServer(1);
assertUsedCache(2);
@@ -94,84 +73,67 @@ public class WSLoaderTest {

@Test
public void get_stream_from_cache() throws IOException {
InputStream is = mock(InputStream.class);
InputStream is = IOUtils.toInputStream("is");
when(cache.getStream(ID)).thenReturn(is);
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client, props);
WSLoaderResult<InputStream> result = loader.loadStream(ID);
assertThat(result.get()).isEqualTo(is);
verify(cache).getStream(ID);

verifyNoMoreInteractions(cache, client);
}

@Test
public void put_stream_in_cache() throws IOException {
InputStream is1 = mock(InputStream.class);
InputStream is2 = mock(InputStream.class);

when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenReturn(is1);
when(cache.getStream(ID)).thenReturn(is2);

WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws);
WSLoaderResult<InputStream> result = loader.loadStream(ID);
assertThat(result.get()).isEqualTo(is2);

verify(client).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt());
verify(cache).put(ID, is1);
assertThat(result.get()).isEqualTo(is);
verify(cache).getStream(ID);

verifyNoMoreInteractions(cache, client);
verifyNoMoreInteractions(cache, ws);
}

@Test
public void default_timeout() throws IOException {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
loader.loadStream(ID);

verify(client).load(anyString(), anyString(), anyBoolean(), anyInt(), eq(60_000));

verifyNoMoreInteractions(client);
}

@Test
public void change_timeout() throws IOException {
when(props.properties()).thenReturn(ImmutableMap.of(WSLoader.SONAR_WS_TIMEOUT_PROPS, "20"));
when(props.property(WSLoader.SONAR_WS_TIMEOUT_PROPS)).thenReturn("20");
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
loader.loadStream(ID);

verify(client).load(anyString(), anyString(), anyBoolean(), anyInt(), eq(20_000));

verifyNoMoreInteractions(client);
public void put_stream_in_cache() throws IOException {
InputStream input = IOUtils.toInputStream("is");

when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input));
when(cache.getStream(ID)).thenReturn(input);

// SERVER_FIRST -> load from server then put to cache
WSLoader underTest = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
WSLoaderResult<InputStream> result = underTest.loadStream(ID);
assertThat(result.get()).isEqualTo(input);

WsConnector wsConnector = ws.wsConnector();
InOrder inOrder = inOrder(wsConnector, cache);
inOrder.verify(wsConnector).call(any(WsRequest.class));
inOrder.verify(cache).put(eq(ID), any(InputStream.class));
inOrder.verify(cache).getStream(ID);
verifyNoMoreInteractions(cache, wsConnector);
}

@Test
public void test_cache_strategy_fallback() throws IOException {
turnCacheEmpty();
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client, props);
when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws);

assertResult(loader.loadString(ID), serverValue, false);

InOrder inOrder = Mockito.inOrder(client, cache);
InOrder inOrder = inOrder(ws.wsConnector(), cache);
inOrder.verify(cache).getString(ID);
inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt());
inOrder.verify(ws.wsConnector()).call(any(WsRequest.class));
}

@Test
public void test_server_strategy_fallback() throws IOException {
turnServerOffline();
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
when(cache.getString(ID)).thenReturn(cacheValue);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);

assertResult(loader.loadString(ID), cacheValue, true);

InOrder inOrder = Mockito.inOrder(client, cache);
inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt());
InOrder inOrder = inOrder(ws.wsConnector(), cache);
inOrder.verify(ws.wsConnector()).call(any(WsRequest.class));
inOrder.verify(cache).getString(ID);
}

@Test
public void test_put_cache() throws IOException {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
loader.loadString(ID);
verify(cache).put(ID, serverValue.getBytes());
}
@@ -181,7 +143,7 @@ public class WSLoaderTest {
turnServerOffline();

when(cache.getString(ID)).thenThrow(new NullPointerException());
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);

try {
loader.loadString(ID);
@@ -196,7 +158,7 @@ public class WSLoaderTest {
public void test_throw_cache_exception() throws IOException {
when(cache.getString(ID)).thenThrow(new IllegalStateException());

WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws);

try {
loader.loadString(ID);
@@ -209,12 +171,12 @@ public class WSLoaderTest {

@Test
public void test_throw_http_exceptions() {
HttpDownloader.HttpException httpException = mock(HttpDownloader.HttpException.class);
HttpException httpException = new HttpException("url", 500, "Internal Error");
IllegalStateException wrapperException = new IllegalStateException(httpException);

when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(wrapperException);
when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(wrapperException);

WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);

try {
loader.loadString(ID);
@@ -230,9 +192,9 @@ public class WSLoaderTest {
turnServerOffline();

exception.expect(IllegalStateException.class);
exception.expectMessage(Matchers.is("Server is not available"));
exception.expectMessage(Matchers.containsString("Server is not available"));

WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, ws);
loader.loadString(ID);
}

@@ -244,7 +206,7 @@ public class WSLoaderTest {
exception.expect(IllegalStateException.class);
exception.expectMessage(Matchers.is("Server is not accessible and data is not cached"));

WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
loader.loadString(ID);
}

@@ -255,13 +217,14 @@ public class WSLoaderTest {
exception.expect(IllegalStateException.class);
exception.expectMessage(Matchers.is("Data is not cached"));

WSLoader loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, ws);
loader.loadString(ID);
}

@Test
public void test_server_strategy() throws IOException {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
assertResult(loader.loadString(ID), serverValue, false);

// should not fetch from cache
@@ -272,13 +235,14 @@ public class WSLoaderTest {
@Test(expected = IllegalStateException.class)
public void test_server_only() throws IOException {
turnServerOffline();
WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client, props);
WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, ws);
loader.loadString(ID);
}

@Test
public void test_string() {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, props);
when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
assertResult(loader.loadString(ID), serverValue, false);
}

@@ -287,14 +251,7 @@ public class WSLoaderTest {
}

private void assertUsedServer(int times) {
verify(client, times(times)).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt());
}

private void assertResult(WSLoaderResult<InputStream> result, byte[] expected, boolean fromCache) throws IOException {
byte[] content = IOUtils.toByteArray(result.get());
assertThat(result).isNotNull();
assertThat(content).isEqualTo(expected);
assertThat(result.isFromCache()).isEqualTo(fromCache);
verify(ws.wsConnector(), times(times)).call(any(WsRequest.class));
}

private void assertResult(WSLoaderResult<String> result, String expected, boolean fromCache) {
@@ -304,7 +261,7 @@ public class WSLoaderTest {
}

private void turnServerOffline() {
when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException());
when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(new IllegalStateException());
}

private void turnCacheEmpty() throws IOException {

+ 0
- 111
sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java Vedi File

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

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.batch.bootstrap.MockHttpServer;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.bootstrap.Slf4jLogger;
import org.sonar.batch.bootstrap.UserProperties;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.sonar.home.cache.DirectoryLock;
import org.sonar.home.cache.PersistentCache;
import org.sonar.home.cache.TTLCacheInvalidation;

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

public class WSLoaderWithServerTest {
private static final String RESPONSE_STRING = "this is the content";
@Rule
public TemporaryFolder temp = new TemporaryFolder();

private MockHttpServer server;
private PersistentCache cache;
private ServerClient client;
private WSLoader loader;
private UserProperties userProps;

@Before
public void setUp() throws Exception {
server = new MockHttpServer();
server.start();

GlobalProperties bootstrapProps = mock(GlobalProperties.class);
when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort());

client = new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4"));
cache = new PersistentCache(temp.getRoot().toPath(), new TTLCacheInvalidation(100_000L), new Slf4jLogger(), mock(DirectoryLock.class));
userProps = mock(UserProperties.class);
}

@After
public void tearDown() {
if (server != null) {
server.stop();
}
}

@Test
public void testCacheOnly() {
loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client, userProps);
makeRequests();

loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, client, userProps);
makeRequests();
assertThat(server.getNumberRequests()).isEqualTo(3);
}

@Test
public void testCacheFirst() {
loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client, userProps);
makeRequests();
assertThat(server.getNumberRequests()).isEqualTo(1);
}

@Test
public void testServerFirst() {
loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client, userProps);
makeRequests();
assertThat(server.getNumberRequests()).isEqualTo(3);
}

@Test
public void testCacheStrategyDisabled() {
loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client, userProps);
makeRequests();
assertThat(server.getNumberRequests()).isEqualTo(3);
}

private void makeRequests() {
server.setMockResponseData(RESPONSE_STRING);
assertThat(loader.loadString("/foo").get()).isEqualTo(RESPONSE_STRING);
assertThat(loader.loadString("/foo").get()).isEqualTo(RESPONSE_STRING);
assertThat(loader.loadString("/foo").get()).isEqualTo(RESPONSE_STRING);
}

}

+ 71
- 77
sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java Vedi File

@@ -19,132 +19,126 @@
*/
package org.sonar.batch.report;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.mockito.Mockito;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.batch.analysis.DefaultAnalysisMode;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.scan.ImmutableProjectReactor;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.client.WsClient;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.apache.commons.io.FileUtils.readFileToString;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public class ReportPublisherTest {

private DefaultAnalysisMode mode;

private ImmutableProjectReactor reactor;

private Settings settings;

private ProjectDefinition root;
@Rule
public LogTester logTester = new LogTester();

@Rule
public TemporaryFolder temp = new TemporaryFolder();

DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class);
Settings settings = new Settings();
WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS);
ImmutableProjectReactor reactor = mock(ImmutableProjectReactor.class);
ProjectDefinition root;
AnalysisContextReportPublisher contextPublisher = mock(AnalysisContextReportPublisher.class);

@Before
public void setUp() {
settings = new Settings();
mode = mock(DefaultAnalysisMode.class);
reactor = mock(ImmutableProjectReactor.class);
root = ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot());
when(reactor.getRoot()).thenReturn(root);
when(wsClient.wsConnector().baseUrl()).thenReturn("https://localhost");
}

@Test
public void should_log_successful_analysis() {
settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver/");
ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);

Logger logger = mock(Logger.class);
job.logSuccess(logger, null);

verify(logger).info("ANALYSIS SUCCESSFUL, you can browse {}", "http://myserver/dashboard/index/struts");
verify(logger).info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report.");
verifyNoMoreInteractions(logger);
public void log_and_dump_information_about_report_uploading() throws IOException {
ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);

underTest.logSuccess("TASK-123");

assertThat(logTester.logs(LoggerLevel.INFO))
.contains("ANALYSIS SUCCESSFUL, you can browse https://localhost/dashboard/index/struts")
.contains("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report")
.contains("More about the report processing at https://localhost/api/ce/task?id=TASK-123");

File detailsFile = new File(temp.getRoot(), "analysis-details.json");
JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" +
"\"projectKey\": \"struts\"," +
"\"dashboardUrl\": \"https://localhost/dashboard/index/struts\"," +
"\"ceTaskId\": \"TASK-123\"," +
"\"ceTaskUrl\": \"https://localhost/api/ce/task?id=TASK-123\"" +
"}"
);
}

@Test
public void should_log_successful_analysis_with_ce_task() {
settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver/");
ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);

Logger logger = mock(Logger.class);
job.logSuccess(logger, "abc123");

verify(logger).info("ANALYSIS SUCCESSFUL, you can browse {}", "http://myserver/dashboard/index/struts");
verify(logger).info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report.");
verify(logger).info("More about the report processing at {}", "http://myserver/api/ce/task?id=abc123");
verifyNoMoreInteractions(logger);
public void log_public_url_if_defined() throws IOException {
settings.setProperty(CoreProperties.SERVER_BASE_URL, "https://publicserver/sonarqube");
ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);

underTest.logSuccess("TASK-123");

assertThat(logTester.logs(LoggerLevel.INFO))
.contains("ANALYSIS SUCCESSFUL, you can browse https://publicserver/sonarqube/dashboard/index/struts")
.contains("More about the report processing at https://publicserver/sonarqube/api/ce/task?id=TASK-123");

File detailsFile = new File(temp.getRoot(), "analysis-details.json");
JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" +
"\"projectKey\": \"struts\"," +
"\"dashboardUrl\": \"https://publicserver/sonarqube/dashboard/index/struts\"," +
"\"ceTaskId\": \"TASK-123\"," +
"\"ceTaskUrl\": \"https://publicserver/sonarqube/api/ce/task?id=TASK-123\"" +
"}"
);
}

@Test
public void should_write_json_file() throws IOException {
settings.setProperty(CoreProperties.SERVER_BASE_URL, "http://myserver/");

ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);
job.logSuccess(mock(Logger.class), "abc123");

File jsonFile = new File(temp.getRoot(), "analysis-details.json");
assertThat(jsonFile).exists();
public void log_but_not_dump_information_when_report_is_not_uploaded() {
ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);

String jsonFileContent = new String(Files.readAllBytes(jsonFile.toPath()), StandardCharsets.UTF_8);
String expectedContent = "\"dashboardUrl\":\"http://myserver/dashboard/index/struts\",\"ceTaskUrl\":\"http://myserver/api/ce/task?id=abc123\"";
assertThat(jsonFileContent).contains(expectedContent);
underTest.logSuccess(/* report not uploaded, no server task */null);

}

@Test
public void should_log_successful_issues_analysis() {
when(mode.isIssues()).thenReturn(true);
ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);
assertThat(logTester.logs(LoggerLevel.INFO))
.contains("ANALYSIS SUCCESSFUL")
.doesNotContain("dashboard/index");

Logger logger = mock(Logger.class);
job.logSuccess(logger, null);

verify(logger).info("ANALYSIS SUCCESSFUL");
verifyNoMoreInteractions(logger);
File detailsFile = new File(temp.getRoot(), "analysis-details.json");
assertThat(detailsFile).doesNotExist();
}

@Test
public void should_not_delete_report() throws IOException {
settings.setProperty("sonar.verbose", true);
public void should_not_delete_report_if_property_is_set() throws IOException {
settings.setProperty("sonar.batch.keepReport", true);
Path reportDir = temp.getRoot().toPath().resolve("batch-report");
Files.createDirectory(reportDir);
ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);
ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);

job.start();
job.stop();
underTest.start();
underTest.stop();
assertThat(reportDir).isDirectory();
}

@Test
public void should_delete_report() throws IOException {
public void should_delete_report_by_default() throws IOException {
Path reportDir = temp.getRoot().toPath().resolve("batch-report");
Files.createDirectory(reportDir);
ReportPublisher job = new ReportPublisher(settings, mock(ServerClient.class), mock(Server.class), mock(AnalysisContextReportPublisher.class), reactor, mode,
mock(TempFolder.class), new ReportPublisherStep[0]);
ReportPublisher job = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);

job.start();
job.stop();

+ 7
- 10
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java Vedi File

@@ -19,25 +19,22 @@
*/
package org.sonar.batch.repository;

import org.sonar.api.utils.MessageException;

import com.google.common.io.Resources;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

import org.sonar.api.utils.HttpDownloader.HttpException;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.cache.WSLoader;
import org.sonar.batch.cache.WSLoaderResult;
import org.sonarqube.ws.WsBatch.WsProjectResponse;
import org.sonarqube.ws.client.HttpException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
@@ -56,7 +53,7 @@ public class DefaultProjectRepositoriesLoaderTest {
public void prepare() throws IOException {
wsLoader = mock(WSLoader.class);
InputStream is = mockData();
when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<InputStream>(is, true));
when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true));
loader = new DefaultProjectRepositoriesLoader(wsLoader);
}

@@ -72,13 +69,13 @@ public class DefaultProjectRepositoriesLoaderTest {
InputStream is = mock(InputStream.class);
when(is.read()).thenThrow(IOException.class);

when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<InputStream>(is, false));
when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, false));
loader.load(PROJECT_KEY, false, null);
}

@Test(expected = IllegalStateException.class)
public void failFastHttpError() {
HttpException http = new HttpException(URI.create("uri"), 403);
HttpException http = new HttpException("url", 403, "Forbidden");
IllegalStateException e = new IllegalStateException("http error", http);
when(wsLoader.loadStream(anyString())).thenThrow(e);
loader.load(PROJECT_KEY, false, null);
@@ -89,7 +86,7 @@ public class DefaultProjectRepositoriesLoaderTest {
thrown.expect(MessageException.class);
thrown.expectMessage("http error");
HttpException http = new HttpException(URI.create("uri"), 403);
HttpException http = new HttpException("uri", 403, "Forbidden");
MessageException e = MessageException.of("http error", http);
when(wsLoader.loadStream(anyString())).thenThrow(e);
loader.load(PROJECT_KEY, false, null);

+ 18
- 26
sonar-ws/pom.xml Vedi File

@@ -13,20 +13,30 @@
<name>SonarQube :: Web Service</name>
<description>Protocol Buffers specification of Web Services</description>

<properties>
<sonar.exclusions>target/generated-sources/**/*</sonar.exclusions>
<sonar.test.exclusions>target/generated-test-sources/**/*</sonar.test.exclusions>
</properties>

<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
@@ -36,32 +46,14 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sonar-testing-harness</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>com.squareup.okhttp</groupId>
<artifactId>mockwebserver</artifactId>
<version>${okhttp.version}</version>
<scope>test</scope>
</dependency>

<!-- Jetty dependencies -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
</dependency>
<dependency>
<groupId>com.github.kevinsawicki</groupId>
<artifactId>http-request</artifactId>
</dependency>
</dependencies>
</project>

+ 2
- 1
sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java Vedi File

@@ -27,7 +27,7 @@ import java.util.Locale;
import java.util.Map;

/**
* @since 3.1
* @since 5.3
*/
public final class MediaTypes {

@@ -35,6 +35,7 @@ public final class MediaTypes {
public static final String XML = "application/xml";
public static final String TXT = "text/plain";
public static final String PROTOBUF = "application/x-protobuf";
public static final String ZIP = "application/zip";
public static final String DEFAULT = "application/octet-stream";

private static final Map<String, String> MAP = new ImmutableMap.Builder<String, String>()

+ 75
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java Vedi File

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

import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.sonarqube.ws.MediaTypes;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Objects.requireNonNull;

abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {

private final String path;

private String mediaType = MediaTypes.JSON;

// keep the same order -> do not use HashMap
private final Map<String, String> params = new LinkedHashMap<>();

BaseRequest(String path) {
this.path = path;
}

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

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

/**
* Expected media type of response. Default is {@link MediaTypes#JSON}.
*/
public SELF setMediaType(String s) {
requireNonNull(s, "media type of response cannot be null");
this.mediaType = s;
return (SELF) this;
}

public SELF setParam(String key, @Nullable Object value) {
checkArgument(!isNullOrEmpty(key), "a WS parameter key cannot be null");
if (value != null) {
this.params.put(key, value.toString());
}
return (SELF) this;
}

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

+ 63
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java Vedi File

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

import com.google.protobuf.Message;
import com.google.protobuf.Parser;
import java.io.InputStream;
import org.sonarqube.ws.MediaTypes;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;

public abstract class BaseService {

private final WsConnector wsConnector;
private final String controller;

public BaseService(WsConnector wsConnector, String controllerPath) {
checkArgument(!isNullOrEmpty(controllerPath));
this.wsConnector = wsConnector;
this.controller = controllerPath;
}

protected <T extends Message> T call(BaseRequest request, Parser<T> parser) {
request.setMediaType(MediaTypes.PROTOBUF);
WsResponse response = wsConnector.call(request);
return convert(response, parser);
}

protected WsResponse call(WsRequest request) {
return wsConnector.call(request);
}

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

protected String path(String action) {
return String.format("%s/%s", controller, action);
}
}

+ 34
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java Vedi File

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

/**
* @since 5.3
*/
public class GetRequest extends BaseRequest<GetRequest> {
public GetRequest(String path) {
super(path);
}

@Override
public Method getMethod() {
return Method.GET;
}
}

+ 182
- 65
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java Vedi File

@@ -17,73 +17,192 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonarqube.ws.client;

import com.google.protobuf.Message;
import com.google.protobuf.Parser;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import static java.lang.String.format;

public class HttpConnector implements WsConnector {

public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30000;
public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60000;
public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30_000;
public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60_000;

/**
* Visibility relaxed for unit tests
* Base URL with trailing slash, for instance "https://localhost/sonarqube/".
* It is required for further usage of {@link HttpUrl#resolve(String)}.
*/
final HttpRequestFactory requestFactory;
private final HttpUrl baseUrl;
private final String userAgent;
private final String credentials;
private final String proxyCredentials;
private final OkHttpClient okHttpClient = new OkHttpClient();

private HttpConnector(Builder builder) {
this.requestFactory = new HttpRequestFactory(builder.url)
.setLogin(builder.login)
.setPassword(builder.password)
.setProxyHost(builder.proxyHost)
.setProxyPort(builder.proxyPort)
.setProxyLogin(builder.proxyLogin)
.setProxyPassword(builder.proxyPassword)
.setConnectTimeoutInMilliseconds(builder.connectTimeoutMs)
.setReadTimeoutInMilliseconds(builder.readTimeoutMs);
this.baseUrl = HttpUrl.parse(builder.url.endsWith("/") ? builder.url : format("%s/", builder.url));
this.userAgent = builder.userAgent;

if (isNullOrEmpty(builder.login)) {
// no login nor access token
this.credentials = null;
} else {
// password is null when login represents an access token. In this case
// the Basic credentials consider an empty password.
this.credentials = Credentials.basic(builder.login, nullToEmpty(builder.password));
}

if (builder.proxy != null) {
this.okHttpClient.setProxy(builder.proxy);
}
// proxy credentials can be used on system-wide proxies, so even if builder.proxy is null
if (isNullOrEmpty(builder.proxyLogin)) {
this.proxyCredentials = null;
} else {
this.proxyCredentials = Credentials.basic(builder.proxyLogin, nullToEmpty(builder.proxyPassword));
}

this.okHttpClient.setConnectTimeout(builder.connectTimeoutMs, TimeUnit.MILLISECONDS);
this.okHttpClient.setReadTimeout(builder.readTimeoutMs, TimeUnit.MILLISECONDS);
this.okHttpClient.interceptors().addAll(builder.interceptors);
}

@Override
public String execute(WsRequest wsRequest) {
return requestFactory.execute(wsRequest);
public String baseUrl() {
return baseUrl.url().toExternalForm();
}

public OkHttpClient okHttpClient() {
return okHttpClient;
}

@CheckForNull
public String userAgent() {
return userAgent;
}

@CheckForNull
public String credentials() {
return credentials;
}

@Override
public <T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser) {
return requestFactory.execute(wsRequest, protobufParser);
public WsResponse call(WsRequest httpRequest) {
if (httpRequest instanceof GetRequest) {
return get((GetRequest) httpRequest);
}
if (httpRequest instanceof PostRequest) {
return post((PostRequest) httpRequest);
}
throw new IllegalArgumentException(format("Unsupported implementation: %s", httpRequest.getClass()));
}

/**
* Create a builder of {@link WsClient}s.
*/
public static Builder newHttpConnector() {
return new Builder();
private WsResponse get(GetRequest getRequest) {
HttpUrl.Builder urlBuilder = prepareUrlBuilder(getRequest);
Request.Builder okRequestBuilder = prepareOkRequestBuilder(getRequest, urlBuilder).get();
return doCall(okRequestBuilder.build());
}

/**
* Create a client with default configuration. Use {@link #newHttpConnector()} to define
* a custom configuration (credentials, HTTP proxy, HTTP timeouts).
*/
public static HttpConnector newDefaultHttpConnector(String serverUrl) {
return newHttpConnector().url(serverUrl).build();
private WsResponse post(PostRequest postRequest) {
HttpUrl.Builder urlBuilder = prepareUrlBuilder(postRequest);
Request.Builder okRequestBuilder = prepareOkRequestBuilder(postRequest, urlBuilder);

Map<String, PostRequest.Part> parts = postRequest.getParts();
if (parts.isEmpty()) {
okRequestBuilder.post(RequestBody.create(null, ""));
} else {
MultipartBuilder body = new MultipartBuilder().type(MultipartBuilder.FORM);
for (Map.Entry<String, PostRequest.Part> param : parts.entrySet()) {
PostRequest.Part part = param.getValue();
body.addPart(
Headers.of("Content-Disposition", format("form-data; name=\"%s\"", param.getKey())),
RequestBody.create(MediaType.parse(part.getMediaType()), part.getFile()));
}
okRequestBuilder.post(body.build());
}

return doCall(okRequestBuilder.build());
}

private HttpUrl.Builder prepareUrlBuilder(WsRequest wsRequest) {
String path = wsRequest.getPath();
HttpUrl.Builder urlBuilder = baseUrl
.resolve(path.startsWith("/") ? path.replaceAll("^(/)+", "") : path)
.newBuilder();
for (Map.Entry<String, String> param : wsRequest.getParams().entrySet()) {
urlBuilder.addQueryParameter(param.getKey(), param.getValue());
}
return urlBuilder;
}

private Request.Builder prepareOkRequestBuilder(WsRequest getRequest, HttpUrl.Builder urlBuilder) {
Request.Builder okHttpRequestBuilder = new Request.Builder()
.url(urlBuilder.build())
.addHeader("Accept", getRequest.getMediaType())
.addHeader("Accept-Charset", "UTF-8");
if (credentials != null) {
okHttpRequestBuilder.header("Authorization", credentials);
}
if (proxyCredentials != null) {
okHttpRequestBuilder.header("Proxy-Authorization", proxyCredentials);
}
if (userAgent != null) {
okHttpRequestBuilder.addHeader("User-Agent", userAgent);
}
return okHttpRequestBuilder;
}

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

public static class Builder {
private String url;
private String userAgent;
private String login;
private String password;
private String url;
private String proxyHost;
private Proxy proxy;
private String proxyLogin;
private String proxyPassword;
private int proxyPort = 0;

private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS;
private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS;
private final List<Interceptor> interceptors = new ArrayList<>();

private Builder() {
/**
* Optional User Agent
*/
public Builder userAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
return this;
}

/**
@@ -95,43 +214,26 @@ public class HttpConnector implements WsConnector {
}

/**
* Optional login, for example "admin"
* Optional login/password, for example "admin"
*/
public Builder login(@Nullable String login) {
public Builder credentials(@Nullable String login, @Nullable String password) {
this.login = login;
return this;
}

/**
* Optional password related to {@link #login(String)}, for example "admin"
*/
public Builder password(@Nullable String password) {
this.password = password;
return this;
}

/**
* Host and port of the optional HTTP proxy
* Optional access token, for example {@code "ABCDE"}. Alternative to {@link #credentials(String, String)}
*/
public Builder proxy(@Nullable String proxyHost, int proxyPort) {
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
return this;
}

public Builder proxyLogin(@Nullable String proxyLogin) {
this.proxyLogin = proxyLogin;
return this;
}

public Builder proxyPassword(@Nullable String proxyPassword) {
this.proxyPassword = proxyPassword;
public Builder token(@Nullable String token) {
this.login = token;
this.password = null;
return this;
}

/**
* Sets a specified timeout value, in milliseconds, to be used when opening HTTP connection.
* A timeout of zero is interpreted as an infinite timeout. Default value is {@link HttpConnector#DEFAULT_CONNECT_TIMEOUT_MILLISECONDS}
* A timeout of zero is interpreted as an infinite timeout. Default value is {@link #DEFAULT_CONNECT_TIMEOUT_MILLISECONDS}
*/
public Builder connectTimeoutMilliseconds(int i) {
this.connectTimeoutMs = i;
@@ -140,20 +242,35 @@ public class HttpConnector implements WsConnector {

/**
* Sets the read timeout to a specified timeout, in milliseconds.
* A timeout of zero is interpreted as an infinite timeout. Default value is {@link HttpConnector#DEFAULT_READ_TIMEOUT_MILLISECONDS}
* A timeout of zero is interpreted as an infinite timeout. Default value is {@link #DEFAULT_READ_TIMEOUT_MILLISECONDS}
*/
public Builder readTimeoutMilliseconds(int i) {
this.readTimeoutMs = i;
return this;
}

public Builder proxy(@Nullable Proxy proxy) {
this.proxy = proxy;
return this;
}

public Builder proxyCredentials(@Nullable String proxyLogin, @Nullable String proxyPassword) {
this.proxyLogin = proxyLogin;
this.proxyPassword = proxyPassword;
return this;
}

/**
* Build a new client
* Adds a OkHttp interceptor, for example to log request URLs or response errors.
* See https://github.com/square/okhttp/wiki/Interceptors
*/
public Builder interceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
return this;
}

public HttpConnector build() {
if (url == null || "".equals(url)) {
throw new IllegalStateException("Server URL must be set");
}
checkArgument(!isNullOrEmpty(url), "Server URL is not defined");
return new HttpConnector(this);
}


+ 10
- 7
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java Vedi File

@@ -20,24 +20,27 @@
package org.sonarqube.ws.client;

/**
* @since 3.6
* @since 5.3
*/
public class HttpException extends RuntimeException {

private final String url;
private final int status;
private final int code;

public HttpException(String url, int status, String message) {
super(String.format("Error %d on %s : %s", status, url, message));
public HttpException(String url, int code, String message) {
super(String.format("Error %d on %s : %s", code, url, message));
this.url = url;
this.status = status;
this.code = code;
}

public String url() {
return url;
}

public int status() {
return status;
/**
* @see java.net.HttpURLConnection constants
*/
public int code() {
return code;
}
}

+ 0
- 229
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java Vedi File

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

import com.github.kevinsawicki.http.HttpRequest;
import com.google.common.base.Throwables;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Parser;
import java.io.InputStream;
import java.util.Arrays;
import javax.annotation.Nullable;
import org.sonarqube.ws.MediaTypes;

import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static java.net.HttpURLConnection.HTTP_OK;

/**
* Not an API. Please do not use this class, except maybe for unit tests.
*/
public class HttpRequestFactory {

private static final int[] RESPONSE_SUCCESS = {HTTP_OK, HTTP_CREATED, HTTP_NO_CONTENT};

private final String baseUrl;
private String login;
private String password;
private String proxyHost;
private String proxyLogin;
private String proxyPassword;
private int proxyPort;
private int connectTimeoutInMilliseconds;
private int readTimeoutInMilliseconds;

public HttpRequestFactory(String baseUrl) {
this.baseUrl = baseUrl;
}

public HttpRequestFactory setLogin(@Nullable String login) {
this.login = login;
return this;
}

public HttpRequestFactory setPassword(@Nullable String password) {
this.password = password;
return this;
}

public HttpRequestFactory setProxyHost(@Nullable String proxyHost) {
this.proxyHost = proxyHost;
return this;
}

public HttpRequestFactory setProxyLogin(@Nullable String proxyLogin) {
this.proxyLogin = proxyLogin;
return this;
}

public HttpRequestFactory setProxyPassword(@Nullable String proxyPassword) {
this.proxyPassword = proxyPassword;
return this;
}

public HttpRequestFactory setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
return this;
}

public HttpRequestFactory setConnectTimeoutInMilliseconds(int connectTimeoutInMilliseconds) {
this.connectTimeoutInMilliseconds = connectTimeoutInMilliseconds;
return this;
}

public HttpRequestFactory setReadTimeoutInMilliseconds(int readTimeoutInMilliseconds) {
this.readTimeoutInMilliseconds = readTimeoutInMilliseconds;
return this;
}

public String getBaseUrl() {
return baseUrl;
}

public String getLogin() {
return login;
}

public String getPassword() {
return password;
}

public String getProxyHost() {
return proxyHost;
}

public String getProxyLogin() {
return proxyLogin;
}

public String getProxyPassword() {
return proxyPassword;
}

public int getProxyPort() {
return proxyPort;
}

public int getConnectTimeoutInMilliseconds() {
return connectTimeoutInMilliseconds;
}

public int getReadTimeoutInMilliseconds() {
return readTimeoutInMilliseconds;
}

public String execute(WsRequest wsRequest) {
HttpRequest httpRequest = wsRequestToHttpRequest(wsRequest);
return execute(httpRequest);
}

public <T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser) {
HttpRequest httpRequest = wsRequestToHttpRequest(wsRequest);
InputStream response = executeWithStream(httpRequest);
try {
return protobufParser.parseFrom(response);
} catch (InvalidProtocolBufferException e) {
Throwables.propagate(e);
}

throw new IllegalStateException("Uncatched exception when parsing protobuf response");
}

private HttpRequest wsRequestToHttpRequest(WsRequest wsRequest) {
HttpRequest httpRequest = WsRequest.Method.GET.equals(wsRequest.getMethod())
? HttpRequest.get(buildUrl(wsRequest.getEndpoint()), wsRequest.getParams(), true)
: HttpRequest.post(buildUrl(wsRequest.getEndpoint()), wsRequest.getParams(), true);
httpRequest = prepare(httpRequest);
switch (wsRequest.getMediaType()) {
case PROTOBUF:
httpRequest.accept(MediaTypes.PROTOBUF);
break;
case JSON:
httpRequest.accept(MediaTypes.JSON);
break;
case TEXT:
httpRequest.accept(MediaTypes.TXT);
break;
default:
httpRequest.accept(MediaTypes.DEFAULT);
break;
}

return httpRequest;
}

private String buildUrl(String part) {
StringBuilder url = new StringBuilder();
url.append(baseUrl);
if (!part.startsWith("/")) {
url.append('/');
}
url.append(part);
return url.toString();
}

private static String execute(HttpRequest request) {
try {
checkSuccess(request);
return request.body(HttpRequest.CHARSET_UTF8);
} catch (HttpRequest.HttpRequestException e) {
throw new IllegalStateException("Fail to request " + request.url(), e);
}
}

private static InputStream executeWithStream(HttpRequest request) {
try {
checkSuccess(request);
return request.stream();
} catch (HttpRequest.HttpRequestException e) {
throw new IllegalStateException("Fail to request " + request.url(), e);
}
}

private static void checkSuccess(HttpRequest request) {
boolean isSuccess = Arrays.binarySearch(RESPONSE_SUCCESS, request.code()) >= 0;
if (!isSuccess) {
throw new HttpException(request.url().toString(), request.code(), request.body());
}
}

private HttpRequest prepare(HttpRequest request) {
if (proxyHost != null) {
request.useProxy(proxyHost, proxyPort);
if (proxyLogin != null) {
request.proxyBasic(proxyLogin, proxyPassword);
}
}
request
.acceptGzipEncoding()
.uncompress(true)
.acceptJson()
.acceptCharset(HttpRequest.CHARSET_UTF8)
.connectTimeout(connectTimeoutInMilliseconds)
.readTimeout(readTimeoutInMilliseconds)
.trustAllCerts()
.trustAllHosts();
if (login != null) {
request.basic(login, password);
}
return request;
}
}

+ 90
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java Vedi File

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

import com.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 implements WsResponse {

private final Response okResponse;

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

@Override
public String getRequestUrl() {
return okResponse.request().urlString();
}

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

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

/**
* Get stream of bytes
*/
@Override
public InputStream getContentStream() {
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 getContentReader() {
try {
return okResponse.body().charStream();
} catch (IOException e) {
throw fail(e);
}
}

@Override
public String getContent() {
try {
return okResponse.body().string();
} catch (IOException e) {
throw fail(e);
}
}

private RuntimeException fail(Exception e) {
throw new IllegalStateException("Fail to read response of " + getRequestUrl(), e);
}
}

+ 89
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java Vedi File

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

package org.sonarqube.ws.client;

import org.sonarqube.ws.client.ce.ComputeEngineService;
import org.sonarqube.ws.client.component.ComponentsService;
import org.sonarqube.ws.client.issue.IssuesService;
import org.sonarqube.ws.client.permission.PermissionsService;
import org.sonarqube.ws.client.qualityprofile.QualityProfilesService;
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 ComputeEngineService ceWsClient;
private final PermissionsService permissionsService;
private final ComponentsService componentsService;
private final QualityProfilesService qualityProfilesService;
private final IssuesService issuesService;
private final UserTokensService userTokensService;
private final WsConnector wsConnector;

public HttpWsClient(WsConnector wsConnector) {
this.wsConnector = wsConnector;
this.ceWsClient = new ComputeEngineService(wsConnector);
this.permissionsService = new PermissionsService(wsConnector);
this.componentsService = new ComponentsService(wsConnector);
this.qualityProfilesService = new QualityProfilesService(wsConnector);
this.issuesService = new IssuesService(wsConnector);
this.userTokensService = new UserTokensService(wsConnector);
}

@Override
public WsConnector wsConnector() {
return wsConnector;
}

@Override
public PermissionsService permissions() {
return this.permissionsService;
}

@Override
public ComputeEngineService computeEngine() {
return ceWsClient;
}

@Override
public ComponentsService components() {
return componentsService;
}

@Override
public QualityProfilesService qualityProfiles() {
return qualityProfilesService;
}

@Override
public IssuesService issues() {
return issuesService;
}

@Override
public UserTokensService userTokens() {
return userTokensService;
}
}

+ 108
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java Vedi File

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

import com.google.common.base.Throwables;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.sonarqube.ws.MediaTypes;

import static java.util.Objects.requireNonNull;

public class MockWsResponse implements WsResponse {

private String requestUrl;
private byte[] content;
private String contentType;

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

public MockWsResponse setContentType(String contentType) {
this.contentType = contentType;
return this;
}

public MockWsResponse setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
return this;
}

public MockWsResponse setContent(InputStream is) {
try {
return setContent(IOUtils.toByteArray(is));
} catch (IOException e) {
throw Throwables.propagate(e);
}
}

public MockWsResponse setContent(byte[] b) {
this.content = b;
return this;
}

public MockWsResponse setContent(String s) {
this.content = s.getBytes(StandardCharsets.UTF_8);
return this;
}

@Override
public boolean hasContent() {
return content != null;
}

@Override
public String getRequestUrl() {
requireNonNull(requestUrl);
return requestUrl;
}

@Override
public InputStream getContentStream() {
requireNonNull(content);
return new ByteArrayInputStream(content);
}

@Override
public Reader getContentReader() {
requireNonNull(content);
return new StringReader(new String(content, StandardCharsets.UTF_8));
}

@Override
public String getContent() {
requireNonNull(content);
return new String(content, StandardCharsets.UTF_8);
}

public static MockWsResponse createJson(String json) {
return new MockWsResponse()
.setContentType(MediaTypes.JSON)
.setContentType(json);
}
}

sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java → sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java Vedi File

@@ -17,55 +17,53 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonarqube.ws.client;

import org.junit.rules.ExternalResource;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;

public final class MockHttpServerInterceptor extends ExternalResource {
/**
* @since 5.3
*/
public class PostRequest extends BaseRequest<PostRequest> {

private MockHttpServer server;
private final Map<String, Part> parts = new LinkedHashMap<>();

@Override
protected final void before() throws Throwable {
server = new MockHttpServer();
server.start();
public PostRequest(String path) {
super(path);
}

@Override
protected void after() {
server.stop();
public Method getMethod() {
return Method.POST;
}

public MockHttpServerInterceptor stubResponseBody(String body) {
server.doReturnBody(body);
public PostRequest setPart(String name, Part part) {
this.parts.put(name, part);
return this;
}

public MockHttpServerInterceptor stubStatusCode(int status) {
server.doReturnStatus(status);
return this;
public Map<String, Part> getParts() {
return parts;
}

public String requestedPath() {
return server.requestPath();
}
public static class Part {
private final String mediaType;
private final File file;

public Map requestHeaders() {
return server.requestHeaders();
}
public Part(String mediaType, File file) {
this.mediaType = mediaType;
this.file = file;
}

public Map requestParams() {
return server.requestParams();
}
public String getMediaType() {
return mediaType;
}

public int port() {
return server.getPort();
public File getFile() {
return file;
}
}

public String url() {
return "http://localhost:" + port();
}
}

+ 15
- 59
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java Vedi File

@@ -17,74 +17,30 @@
* 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 com.google.protobuf.Message;
import com.google.protobuf.Parser;
import org.sonarqube.ws.client.component.ComponentsWsClient;
import org.sonarqube.ws.client.issue.IssuesWsClient;
import org.sonarqube.ws.client.permission.PermissionsWsClient;
import org.sonarqube.ws.client.qualityprofile.QualityProfilesWsClient;
import org.sonarqube.ws.client.usertoken.UserTokensWsClient;

import static org.sonarqube.ws.client.WsRequest.MediaType.PROTOBUF;
import org.sonarqube.ws.client.ce.ComputeEngineService;
import org.sonarqube.ws.client.component.ComponentsService;
import org.sonarqube.ws.client.issue.IssuesService;
import org.sonarqube.ws.client.permission.PermissionsService;
import org.sonarqube.ws.client.qualityprofile.QualityProfilesService;
import org.sonarqube.ws.client.usertoken.UserTokensService;

/**
* Entry point of the Java Client for SonarQube Web Services.
* <p/>
* Example:
* <pre>
* WsClient client = new WsClient(Connector);
* </pre>
*
* @since 5.2
* @since 5.3
*/
public class WsClient {

@VisibleForTesting
final WsConnector wsConnector;
private final PermissionsWsClient permissionsWsClient;
private final ComponentsWsClient componentsWsClient;
private final QualityProfilesWsClient qualityProfilesWsClient;
private final IssuesWsClient issuesWsClient;
private final UserTokensWsClient userTokensWsClient;

public WsClient(WsConnector wsConnector) {
this.wsConnector = wsConnector;
this.permissionsWsClient = new PermissionsWsClient(this);
this.componentsWsClient = new ComponentsWsClient(this);
this.qualityProfilesWsClient = new QualityProfilesWsClient(this);
this.issuesWsClient = new IssuesWsClient(this);
userTokensWsClient = new UserTokensWsClient(this);
}

public String execute(WsRequest wsRequest) {
return wsConnector.execute(wsRequest);
}
public interface WsClient {
ComponentsService components();

public <T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser) {
return wsConnector.execute(wsRequest.setMediaType(PROTOBUF), protobufParser);
}
ComputeEngineService computeEngine();

public PermissionsWsClient permissionsClient() {
return this.permissionsWsClient;
}
IssuesService issues();

public ComponentsWsClient componentsWsClient() {
return componentsWsClient;
}
PermissionsService permissions();

public QualityProfilesWsClient qualityProfilesWsClient() {
return qualityProfilesWsClient;
}
QualityProfilesService qualityProfiles();

public IssuesWsClient issuesWsClient() {
return issuesWsClient;
}
UserTokensService userTokens();

public UserTokensWsClient userTokensWsClient() {
return userTokensWsClient;
}
WsConnector wsConnector();
}

+ 17
- 5
sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java Vedi File

@@ -20,11 +20,23 @@

package org.sonarqube.ws.client;

import com.google.protobuf.Message;
import com.google.protobuf.Parser;
/**
* @since 5.3
*/
public interface WsConnector {
String execute(WsRequest wsRequest);

<T extends Message> T execute(WsRequest wsRequest, Parser<T> protobufParser);
/**
* @throws IllegalStateException if the request could not be executed due to
* a connectivity problem or timeout. Because networks can
* fail during an exchange, it is possible that the remote server
* accepted the request before the failure
* @throws HttpException if the response code is not in range [200..300)
*/
WsResponse call(WsRequest wsRequest);

/**
* Server base URL, always with trailing slash, for instance "http://localhost:9000/"
*/
String baseUrl();

}

+ 9
- 64
sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java Vedi File

@@ -20,77 +20,22 @@

package org.sonarqube.ws.client;

import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;

import static java.util.Objects.requireNonNull;
import static org.sonarqube.ws.client.WsRequest.Method.GET;
import static org.sonarqube.ws.client.WsRequest.Method.POST;

public class WsRequest {
private final Map<String, Object> params = new HashMap<>();
private Method method = Method.GET;
private MediaType mimeType = MediaType.JSON;
private String endpoint;

private WsRequest(String endpoint) {
this.endpoint = endpoint;
}

public static WsRequest newPostRequest(String endpoint) {
return new WsRequest(endpoint)
.setMethod(POST);
}

public static WsRequest newGetRequest(String endpoint) {
return new WsRequest(endpoint)
.setMethod(GET);
}

public Method getMethod() {
return method;
}

private WsRequest setMethod(Method method) {
this.method = method;
return this;
}

public MediaType getMediaType() {
return mimeType;
}

public WsRequest setMediaType(MediaType type) {
requireNonNull(type);
this.mimeType = type;
return this;
}
/**
* @since 5.3
*/
public interface WsRequest {

public WsRequest setParam(String key, @Nullable Object value) {
requireNonNull(key, "a WS parameter key cannot be null");
if (value != null) {
this.params.put(key, value);
} else {
this.params.remove(key);
}
Method getMethod();

return this;
}
String getPath();

public String getEndpoint() {
return endpoint;
}
String getMediaType();

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

public enum Method {
enum Method {
GET, POST
}

public enum MediaType {
PROTOBUF, JSON, TEXT
}
}

+ 41
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java Vedi File

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

import java.io.InputStream;
import java.io.Reader;

/**
* @since 5.3
*/
public interface WsResponse {

boolean hasContent();

String getContentType();

String getRequestUrl();

InputStream getContentStream();

Reader getContentReader();

String getContent();
}

+ 44
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java Vedi File

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

package org.sonarqube.ws.client.ce;

import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.WsCe;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;

public class ComputeEngineService extends BaseService {

public ComputeEngineService(WsConnector wsConnector) {
super(wsConnector, "api/ce");
}

public WsCe.SubmitResponse submit(SubmitWsRequest request) {
PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, request.getReport());
PostRequest post = new PostRequest(path("submit"))
.setParam("projectKey", request.getProjectKey())
.setParam("projectName", request.getProjectName())
.setParam("projectBranch", request.getProjectBranch())
.setPart("report", filePart);
return call(post, WsCe.SubmitResponse.parser());
}
}

+ 71
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java Vedi File

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

package org.sonarqube.ws.client.ce;

import java.io.File;
import javax.annotation.CheckForNull;

public class SubmitWsRequest {

private String projectKey;
private String projectName;
private String projectBranch;
private File report;

public String getProjectKey() {
return projectKey;
}

public SubmitWsRequest setProjectKey(String projectKey) {
this.projectKey = projectKey;
return this;
}

@CheckForNull
public String getProjectName() {
return projectName;
}

public SubmitWsRequest setProjectName(String projectName) {
this.projectName = projectName;
return this;
}

@CheckForNull
public String getProjectBranch() {
return projectBranch;
}

public SubmitWsRequest setProjectBranch(String projectBranch) {
this.projectBranch = projectBranch;
return this;
}

@CheckForNull
public File getReport() {
return report;
}

public SubmitWsRequest setReport(File report) {
this.report = report;
return this;
}
}

+ 25
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java Vedi File

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

@ParametersAreNonnullByDefault
package org.sonarqube.ws.client.ce;

import javax.annotation.ParametersAreNonnullByDefault;


sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java Vedi File

@@ -21,29 +21,22 @@
package org.sonarqube.ws.client.component;

import org.sonarqube.ws.WsComponents.SearchWsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.WsRequest.newGetRequest;
public class ComponentsService extends BaseService {

public class ComponentsWsClient {
private static final String ENDPOINT = "api/components/";
private final WsClient wsClient;

public ComponentsWsClient(WsClient wsClient) {
this.wsClient = wsClient;
public ComponentsService(WsConnector wsConnector) {
super(wsConnector, "api/components");
}

public SearchWsResponse search(SearchWsRequest request) {
return wsClient.execute(
newGetRequest(action("search"))
.setParam("qualifiers", request.getQualifiers())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery()),
SearchWsResponse.parser());
}

private static String action(String action) {
return ENDPOINT + action;
GetRequest get = new GetRequest(path("search"))
.setParam("qualifiers", request.getQualifiers())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery());
return call(get, SearchWsResponse.parser());
}
}

+ 1
- 1
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java Vedi File

@@ -26,7 +26,7 @@ import com.google.common.collect.Iterables;
import java.util.List;

/**
* @since 3.7
* @since 5.3
*/
public class IssueFilterParameters {


sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java Vedi File

@@ -25,9 +25,10 @@ import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonarqube.ws.Issues.SearchWsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.WsRequest.newGetRequest;
import static org.sonarqube.ws.client.issue.IssueFilterParameters.ACTION_PLANS;
import static org.sonarqube.ws.client.issue.IssueFilterParameters.ADDITIONAL_FIELDS;
import static org.sonarqube.ws.client.issue.IssueFilterParameters.ASC;
@@ -62,17 +63,16 @@ import static org.sonarqube.ws.client.issue.IssueFilterParameters.SEVERITIES;
import static org.sonarqube.ws.client.issue.IssueFilterParameters.STATUSES;
import static org.sonarqube.ws.client.issue.IssueFilterParameters.TAGS;

public class IssuesWsClient {
public class IssuesService extends BaseService {
private static final Joiner LIST_TO_PARAMS_STRING = Joiner.on(",").skipNulls();
private final WsClient wsClient;

public IssuesWsClient(WsClient wsClient) {
this.wsClient = wsClient;
public IssuesService(WsConnector wsConnector) {
super(wsConnector, "api/issues");
}

public SearchWsResponse search(SearchWsRequest request) {
return wsClient.execute(
newGetRequest(action("search"))
return call(
new GetRequest(path("search"))
.setParam(ACTION_PLANS, listToParamList(request.getActionPlans()))
.setParam(ADDITIONAL_FIELDS, listToParamList(request.getAdditionalFields()))
.setParam(ASC, request.getAsc())
@@ -113,10 +113,6 @@ public class IssuesWsClient {
SearchWsResponse.parser());
}

private static String action(String action) {
return "api/issues/" + action;
}

@CheckForNull
private static String listToParamList(@Nullable List<String> strings) {
return strings == null

sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsService.java Vedi File

@@ -27,10 +27,11 @@ import org.sonarqube.ws.WsPermissions.SearchTemplatesWsResponse;
import org.sonarqube.ws.WsPermissions.UpdateTemplateWsResponse;
import org.sonarqube.ws.WsPermissions.UsersWsResponse;
import org.sonarqube.ws.WsPermissions.WsSearchGlobalPermissionsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.WsRequest.newGetRequest;
import static org.sonarqube.ws.client.WsRequest.newPostRequest;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_DESCRIPTION;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_ID;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_NAME;
@@ -45,27 +46,26 @@ import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_T
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_TEMPLATE_NAME;
import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_USER_LOGIN;

public class PermissionsWsClient {
private final WsClient wsClient;
public class PermissionsService extends BaseService {

public PermissionsWsClient(WsClient wsClient) {
this.wsClient = wsClient;
public PermissionsService(WsConnector wsConnector) {
super(wsConnector, PermissionsWsParameters.CONTROLLER);
}

public WsPermissions.WsGroupsResponse groups(GroupsWsRequest request) {
return wsClient.execute(newGetRequest(action("groups"))
GetRequest get = new GetRequest(path("groups"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("selected", request.getSelected())
.setParam("q", request.getQuery()),
WsPermissions.WsGroupsResponse.parser());
.setParam("q", request.getQuery());
return call(get, WsPermissions.WsGroupsResponse.parser());
}

public void addGroup(AddGroupWsRequest request) {
wsClient.execute(newPostRequest(action("add_group"))
call(new PostRequest(path("add_group"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
@@ -74,7 +74,7 @@ public class PermissionsWsClient {
}

public void addGroupToTemplate(AddGroupToTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("add_group_to_template"))
call(new PostRequest(path("add_group_to_template"))
.setParam(PARAM_GROUP_ID, request.getGroupId())
.setParam(PARAM_GROUP_NAME, request.getGroupName())
.setParam(PARAM_PERMISSION, request.getPermission())
@@ -83,7 +83,7 @@ public class PermissionsWsClient {
}

public void addUser(AddUserWsRequest request) {
wsClient.execute(newPostRequest(action("add_user"))
call(new PostRequest(path("add_user"))
.setParam(PARAM_USER_LOGIN, request.getLogin())
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
@@ -91,7 +91,7 @@ public class PermissionsWsClient {
}

public void addUserToTemplate(AddUserToTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("add_user_to_template"))
call(new PostRequest(path("add_user_to_template"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_USER_LOGIN, request.getLogin())
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
@@ -99,7 +99,7 @@ public class PermissionsWsClient {
}

public void applyTemplate(ApplyTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("apply_template"))
call(new PostRequest(path("apply_template"))
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
@@ -107,22 +107,21 @@ public class PermissionsWsClient {
}

public CreateTemplateWsResponse createTemplate(CreateTemplateWsRequest request) {
return wsClient.execute(newPostRequest(
action("create_template"))
.setParam(PARAM_NAME, request.getName())
.setParam(PARAM_DESCRIPTION, request.getDescription())
.setParam(PARAM_PROJECT_KEY_PATTERN, request.getProjectKeyPattern()),
CreateTemplateWsResponse.parser());
PostRequest post = new PostRequest(path("create_template"))
.setParam(PARAM_NAME, request.getName())
.setParam(PARAM_DESCRIPTION, request.getDescription())
.setParam(PARAM_PROJECT_KEY_PATTERN, request.getProjectKeyPattern());
return call(post, CreateTemplateWsResponse.parser());
}

public void deleteTemplate(DeleteTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("delete_template"))
call(new PostRequest(path("delete_template"))
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
.setParam(PARAM_TEMPLATE_NAME, request.getTemplateName()));
}

public void removeGroup(RemoveGroupWsRequest request) {
wsClient.execute(newPostRequest(action("remove_group"))
call(new PostRequest(path("remove_group"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_GROUP_ID, request.getGroupId())
.setParam(PARAM_GROUP_NAME, request.getGroupName())
@@ -131,7 +130,7 @@ public class PermissionsWsClient {
}

public void removeGroupFromTemplate(RemoveGroupFromTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("remove_group_from_template"))
call(new PostRequest(path("remove_group_from_template"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_GROUP_ID, request.getGroupId())
.setParam(PARAM_GROUP_NAME, request.getGroupName())
@@ -140,7 +139,7 @@ public class PermissionsWsClient {
}

public void removeUser(RemoveUserWsRequest request) {
wsClient.execute(newPostRequest(action("remove_user"))
call(new PostRequest(path("remove_user"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_USER_LOGIN, request.getLogin())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
@@ -148,7 +147,7 @@ public class PermissionsWsClient {
}

public void removeUserFromTemplate(RemoveUserFromTemplateWsRequest request) {
wsClient.execute(newPostRequest(action("remove_user_from_template"))
call(new PostRequest(path("remove_user_from_template"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_USER_LOGIN, request.getLogin())
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
@@ -156,62 +155,50 @@ public class PermissionsWsClient {
}

public WsSearchGlobalPermissionsResponse searchGlobalPermissions() {
return wsClient.execute(
newGetRequest(action("search_global_permissions")),
WsSearchGlobalPermissionsResponse.parser());
GetRequest get = new GetRequest(path("search_global_permissions"));
return call(get, WsSearchGlobalPermissionsResponse.parser());
}

public SearchProjectPermissionsWsResponse searchProjectPermissions(SearchProjectPermissionsWsRequest request) {
return wsClient.execute(
newGetRequest(action("search_project_permissions"))
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam(PARAM_QUALIFIER, request.getQualifier())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery()),
SearchProjectPermissionsWsResponse.parser());
GetRequest get = new GetRequest(path("search_project_permissions"))
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam(PARAM_QUALIFIER, request.getQualifier())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery());
return call(get, SearchProjectPermissionsWsResponse.parser());
}

public SearchTemplatesWsResponse searchTemplates(SearchTemplatesWsRequest request) {
return wsClient.execute(
newGetRequest(action("search_templates"))
.setParam("q", request.getQuery()),
SearchTemplatesWsResponse.parser());
GetRequest get = new GetRequest(path("search_templates"))
.setParam("q", request.getQuery());
return call(get, SearchTemplatesWsResponse.parser());
}

public void setDefaultTemplate(SetDefaultTemplateWsRequest request) {
wsClient.execute(
newPostRequest(action("set_default_template"))
.setParam(PARAM_QUALIFIER, request.getQualifier())
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
.setParam(PARAM_TEMPLATE_NAME, request.getTemplateName()));
call(new PostRequest(path("set_default_template"))
.setParam(PARAM_QUALIFIER, request.getQualifier())
.setParam(PARAM_TEMPLATE_ID, request.getTemplateId())
.setParam(PARAM_TEMPLATE_NAME, request.getTemplateName()));
}

public UpdateTemplateWsResponse updateTemplate(UpdateTemplateWsRequest request) {
return wsClient.execute(
newPostRequest(action("update_template"))
.setParam(PARAM_DESCRIPTION, request.getDescription())
.setParam(PARAM_ID, request.getId())
.setParam(PARAM_NAME, request.getName())
.setParam(PARAM_PROJECT_KEY_PATTERN, request.getProjectKeyPattern()),
UpdateTemplateWsResponse.parser());
return call(new PostRequest(path("update_template"))
.setParam(PARAM_DESCRIPTION, request.getDescription())
.setParam(PARAM_ID, request.getId())
.setParam(PARAM_NAME, request.getName())
.setParam(PARAM_PROJECT_KEY_PATTERN, request.getProjectKeyPattern()), UpdateTemplateWsResponse.parser());
}

public UsersWsResponse users(UsersWsRequest request) {
return wsClient.execute(
newGetRequest(action("users"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam("selected", request.getSelected())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery()),
UsersWsResponse.parser());
}

private static String action(String action) {
return PermissionsWsParameters.ENDPOINT + "/" + action;
return call(new GetRequest(path("users"))
.setParam(PARAM_PERMISSION, request.getPermission())
.setParam(PARAM_PROJECT_ID, request.getProjectId())
.setParam(PARAM_PROJECT_KEY, request.getProjectKey())
.setParam("selected", request.getSelected())
.setParam("p", request.getPage())
.setParam("ps", request.getPageSize())
.setParam("q", request.getQuery()), UsersWsResponse.parser());
}
}

+ 1
- 1
sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java Vedi File

@@ -21,7 +21,7 @@
package org.sonarqube.ws.client.permission;

public class PermissionsWsParameters {
public static final String ENDPOINT = "api/permissions";
public static final String CONTROLLER = "api/permissions";

public static final String PARAM_PERMISSION = "permission";
public static final String PARAM_GROUP_NAME = "groupName";

sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java Vedi File

@@ -21,20 +21,19 @@
package org.sonarqube.ws.client.qualityprofile;

import org.sonarqube.ws.QualityProfiles.SearchWsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.WsRequest.newGetRequest;
public class QualityProfilesService extends BaseService {

public class QualityProfilesWsClient {
private final WsClient wsClient;

public QualityProfilesWsClient(WsClient wsClient) {
this.wsClient = wsClient;
public QualityProfilesService(WsConnector wsConnector) {
super(wsConnector, "api/qualityprofiles");
}

public SearchWsResponse search(SearchWsRequest request) {
return wsClient.execute(
newGetRequest(action("search"))
return call(
new GetRequest(path("search"))
.setParam("defaults", request.getDefaults())
.setParam("language", request.getLanguage())
.setParam("profileName", request.getProfileName())
@@ -42,7 +41,4 @@ public class QualityProfilesWsClient {
SearchWsResponse.parser());
}

private static String action(String action) {
return "api/qualityprofiles/" + action;
}
}

sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java → sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensService.java Vedi File

@@ -20,51 +20,27 @@

package org.sonarqube.ws.client.usertoken;

import org.sonarqube.ws.WsComponents.SearchWsResponse;
import org.sonarqube.ws.WsUserTokens.GenerateWsResponse;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.WsRequest.newGetRequest;
import static org.sonarqube.ws.client.WsRequest.newPostRequest;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_GENERATE;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_REVOKE;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.USER_TOKENS_ENDPOINT;

public class UserTokensWsClient {
private static final String SLASH = "/";
private final WsClient wsClient;
public class UserTokensService extends BaseService {

public UserTokensWsClient(WsClient wsClient) {
this.wsClient = wsClient;
public UserTokensService(WsConnector wsConnector) {
super(wsConnector, CONTROLLER);
}

public GenerateWsResponse generate(GenerateWsRequest request) {
return wsClient.execute(
newPostRequest(action(ACTION_GENERATE))
return call(
new PostRequest(path(ACTION_GENERATE))
.setParam(PARAM_LOGIN, request.getLogin())
.setParam(PARAM_NAME, request.getName()),
GenerateWsResponse.parser());
}

public void revoke(RevokeWsRequest request) {
wsClient.execute(
newPostRequest(action(ACTION_REVOKE))
.setParam(PARAM_LOGIN, request.getLogin())
.setParam(PARAM_NAME, request.getName()));
}

public SearchWsResponse search(SearchWsRequest request) {
return wsClient.execute(
newGetRequest(action(ACTION_SEARCH))
.setParam(PARAM_LOGIN, request.getLogin()),
SearchWsResponse.parser()
);
}

private static String action(String action) {
return USER_TOKENS_ENDPOINT + SLASH + action;
}
}

+ 1
- 1
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java Vedi File

@@ -21,7 +21,7 @@
package org.sonarqube.ws.client.usertoken;

public class UserTokensWsParameters {
public static final String USER_TOKENS_ENDPOINT = "api/user_tokens";
public static final String CONTROLLER = "api/user_tokens";
public static final String ACTION_GENERATE = "generate";
public static final String ACTION_REVOKE = "revoke";
public static final String ACTION_SEARCH = "search";

+ 84
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java Vedi File

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

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonarqube.ws.MediaTypes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;

public class BaseRequestTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

FakeRequest underTest = new FakeRequest("api/foo");

@Test
public void test_defaults() {
assertThat(underTest.getMethod()).isEqualTo(WsRequest.Method.GET);
assertThat(underTest.getParams()).isEmpty();
assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.JSON);
assertThat(underTest.getPath()).isEqualTo("api/foo");
}

@Test
public void setMediaType() {
underTest.setMediaType(MediaTypes.PROTOBUF);
assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.PROTOBUF);
}

@Test
public void keep_order_of_params() {
assertThat(underTest.getParams()).isEmpty();

underTest.setParam("keyB", "b");
assertThat(underTest.getParams()).containsExactly(entry("keyB", "b"));

underTest.setParam("keyA", "a");
assertThat(underTest.getParams()).containsExactly(entry("keyB", "b"), entry("keyA", "a"));
}

@Test
public void null_param_value() {
underTest.setParam("key", null);
assertThat(underTest.getParams()).isEmpty();
}

@Test
public void fail_if_null_param_key() {
expectedException.expect(IllegalArgumentException.class);
underTest.setParam(null, "val");
}

private static class FakeRequest extends BaseRequest<FakeRequest> {
FakeRequest(String path) {
super(path);
}

@Override
public Method getMethod() {
return Method.GET;
}
}
}

+ 93
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java Vedi File

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

import java.io.IOException;
import org.junit.Test;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Testing;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class BaseServiceTest {

WsConnector wsConnector = mock(WsConnector.class);

@Test
public void test_call() throws Exception {
new BaseService(wsConnector, "api/issues") {

public void test() throws IOException {
GetRequest get = new GetRequest(path("issue")).setMediaType(MediaTypes.JSON);
when(wsConnector.call(get)).thenReturn(new MockWsResponse().setContent("ok"));

WsResponse response = call(get);

assertThat(response.getContent()).isEqualTo("ok");
}

}.test();
}

@Test
public void call_and_convert_protobuf() {
new BaseService(wsConnector, "api/issues") {

public void test() {
GetRequest get = new GetRequest(path("issue")).setParam("key", "ABC");
when(wsConnector.call(get)).thenReturn(newProtobufFakeResponse());

Testing.Fake message = call(get, Testing.Fake.parser());

assertThat(message.getLabel()).isEqualTo("ok");
assertThat(get.getPath()).isEqualTo("api/issues/issue");
// media type automatically set to protobuf
assertThat(get.getMediaType()).isEqualTo(MediaTypes.PROTOBUF);
}

}.test();
}

@Test
public void fail_to_parse_protobuf_response() {
new BaseService(wsConnector, "api/issues") {

public void test() {
GetRequest get = new GetRequest(path("issue")).setParam("key", "ABC");
when(wsConnector.call(get)).thenReturn(MockWsResponse.createJson("{}").setRequestUrl("http://local/api/issues/issue?key=ABC"));

try {
call(get, Testing.Fake.parser());
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Fail to parse protobuf response of http://local/api/issues/issue?key=ABC");
}
}
}.test();
}

private static WsResponse newProtobufFakeResponse() {
Testing.Fake message = Testing.Fake.newBuilder().setLabel("ok").build();
return new MockWsResponse().setContent(message.toByteArray());
}
}

+ 297
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java Vedi File

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

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonarqube.ws.MediaTypes;

import static com.squareup.okhttp.Credentials.basic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;

public class HttpConnectorTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

MockWebServer server;
String serverUrl;

@Before
public void setUp() throws Exception {
server = new MockWebServer();
server.start();
serverUrl = server.url("").url().toString();
}

@Test
public void test_default_settings() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
assertThat(underTest.baseUrl()).isEqualTo(serverUrl);
GetRequest request = new GetRequest("api/issues/search").setMediaType(MediaTypes.PROTOBUF);
WsResponse response = underTest.call(request);

// verify default timeouts on client
assertThat(underTest.okHttpClient().getConnectTimeout()).isEqualTo(HttpConnector.DEFAULT_CONNECT_TIMEOUT_MILLISECONDS);
assertThat(underTest.okHttpClient().getReadTimeout()).isEqualTo(HttpConnector.DEFAULT_READ_TIMEOUT_MILLISECONDS);

// verify response
assertThat(response.hasContent()).isTrue();
assertThat(response.getContent()).isEqualTo("hello, world!");

// verify the request received by server
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod()).isEqualTo("GET");
assertThat(recordedRequest.getPath()).isEqualTo("/api/issues/search");
assertThat(recordedRequest.getHeader("Accept")).isEqualTo(MediaTypes.PROTOBUF);
assertThat(recordedRequest.getHeader("Accept-Charset")).isEqualTo("UTF-8");
assertThat(recordedRequest.getHeader("User-Agent")).startsWith("okhttp/");
// compression is handled by OkHttp
assertThat(recordedRequest.getHeader("Accept-Encoding")).isEqualTo("gzip");
}

@Test
public void use_basic_authentication() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.credentials("theLogin", "thePassword")
.build();

GetRequest request = new GetRequest("api/issues/search");
underTest.call(request);

RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theLogin", "thePassword"));
}

@Test
public void use_basic_authentication_with_null_password() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.credentials("theLogin", null)
.build();

GetRequest request = new GetRequest("api/issues/search");
underTest.call(request);

RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theLogin", ""));
}

/**
* Access token replaces the couple {login,password} and is sent through
* the login field
*/
@Test
public void use_access_token() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.token("theToken")
.build();

GetRequest request = new GetRequest("api/issues/search");
underTest.call(request);

RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(basic("theToken", ""));
}

@Test
public void use_proxy_authentication() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.proxyCredentials("theProxyLogin", "theProxyPassword")
.build();

GetRequest request = new GetRequest("api/issues/search");
underTest.call(request);

RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader("Proxy-Authorization")).isEqualTo(basic("theProxyLogin", "theProxyPassword"));
}

@Test
public void override_timeouts() {
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.readTimeoutMilliseconds(42)
.connectTimeoutMilliseconds(74)
.build();

assertThat(underTest.okHttpClient().getReadTimeout()).isEqualTo(42);
assertThat(underTest.okHttpClient().getConnectTimeout()).isEqualTo(74);
}

@Test
public void send_user_agent() throws Exception {
answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.userAgent("Maven Plugin/2.3")
.build();

underTest.call(new GetRequest("api/issues/search"));

RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("Maven Plugin/2.3");
}

@Test
public void fail_if_unknown_implementation_of_request() {
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
try {
underTest.call(mock(WsRequest.class));
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageContaining("Unsupported implementation: ");
}
}

@Test
public void send_post_request() throws Exception {
answerHelloWorld();
PostRequest request = new PostRequest("api/issues/search")
.setParam("severity", "MAJOR")
.setMediaType(MediaTypes.PROTOBUF);

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

// verify response
assertThat(response.hasContent()).isTrue();
assertThat(response.getContent()).isEqualTo("hello, world!");

// verify the request received by server
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod()).isEqualTo("POST");
assertThat(recordedRequest.getPath()).isEqualTo("/api/issues/search?severity=MAJOR");
}

@Test
public void upload_file() throws Exception {
answerHelloWorld();
File reportFile = temp.newFile();
FileUtils.write(reportFile, "the report content");
PostRequest request = new PostRequest("api/report/upload")
.setParam("project", "theKey")
.setPart("report", new PostRequest.Part(MediaTypes.TXT, reportFile))
.setMediaType(MediaTypes.PROTOBUF);

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

assertThat(response.hasContent()).isTrue();
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod()).isEqualTo("POST");
assertThat(recordedRequest.getPath()).isEqualTo("/api/report/upload?project=theKey");
String body = IOUtils.toString(recordedRequest.getBody().inputStream());
assertThat(body)
.contains("Content-Disposition: form-data; name=\"report\"")
.contains("Content-Type: text/plain")
.contains("the report content");
}

@Test
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();

try {
underTest.call(request);
fail();
} catch (HttpException e) {
assertThat(e.code()).isEqualTo(404);

}
}

@Test
public void intercept_request_and_response() {
final AtomicBoolean called = new AtomicBoolean(false);
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
called.set(true);
return chain.proceed(chain.request());
}
};

answerHelloWorld();
HttpConnector underTest = new HttpConnector.Builder()
.url(serverUrl)
.interceptor(interceptor)
.build();
underTest.call(new GetRequest(""));

assertThat(called.get()).isTrue();
}

@Test
public void support_base_url_ending_with_slash() throws Exception {
assertThat(serverUrl).endsWith("/");
HttpConnector underTest = new HttpConnector.Builder().url(StringUtils.removeEnd(serverUrl, "/")).build();
GetRequest request = new GetRequest("api/issues/search");

answerHelloWorld();
WsResponse response = underTest.call(request);

assertThat(response.hasContent()).isTrue();
}

@Test
public void support_base_url_with_context() {
// just to be sure
assertThat(serverUrl).endsWith("/");
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl + "sonar").build();

GetRequest request = new GetRequest("api/issues/search");
answerHelloWorld();
assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");

request = new GetRequest("/api/issues/search");
answerHelloWorld();
assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
}

private void answerHelloWorld() {
server.enqueue(new MockResponse().setBody("hello, world!"));
}
}

+ 1
- 1
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java Vedi File

@@ -27,7 +27,7 @@ public class HttpExceptionTest {
@Test
public void test_exception() throws Exception {
HttpException exception = new HttpException("http://localhost:9000/api/search", 500, "Not found");
assertThat(exception.status()).isEqualTo(500);
assertThat(exception.code()).isEqualTo(500);
assertThat(exception.url()).isEqualTo("http://localhost:9000/api/search");
assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search : Not found");
}

+ 0
- 86
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java Vedi File

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

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonarqube.ws.client.WsRequest.newGetRequest;

public class HttpRequestFactoryTest {
@Rule
public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void test_get() {
httpServer.stubStatusCode(200).stubResponseBody("{'issues': []}");

HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
String json = factory.execute(newGetRequest("/api/issues"));

assertThat(json).isEqualTo("{'issues': []}");
assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
}

@Test
public void should_throw_illegal_state_exc_if_connect_exception() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Fail to request http://localhost:1/api/issues");

HttpRequestFactory factory = new HttpRequestFactory("http://localhost:1");
factory.execute(newGetRequest("/api/issues"));
}

@Test
public void test_authentication() {
httpServer.stubStatusCode(200).stubResponseBody("{}");

HttpRequestFactory factory = new HttpRequestFactory(httpServer.url()).setLogin("karadoc").setPassword("legrascestlavie");
String json = factory.execute(newGetRequest("/api/issues"));

assertThat(json).isEqualTo("{}");
assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
assertThat(httpServer.requestHeaders().get("Authorization")).isEqualTo("Basic a2FyYWRvYzpsZWdyYXNjZXN0bGF2aWU=");
}

@Test
public void test_proxy() throws Exception {
expectedException.expect(IllegalStateException.class);

HttpRequestFactory factory = new HttpRequestFactory(httpServer.url())
.setProxyHost("localhost").setProxyPort(1)
.setProxyLogin("john").setProxyPassword("smith");
factory.execute(newGetRequest("/api/issues"));
}

@Test
public void beginning_slash_is_optional() throws Exception {
HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
factory.execute(newGetRequest("api/foo"));
assertThat(httpServer.requestedPath()).isEqualTo("/api/foo");

factory.execute(newGetRequest("/api/bar"));
assertThat(httpServer.requestedPath()).isEqualTo("/api/bar");
}
}

+ 0
- 128
sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java Vedi File

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

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;

import static org.apache.commons.io.IOUtils.write;

public class MockHttpServer {
private Server server;
private String responseBody;
private byte[] binaryResponseBody;
private int responseStatus = HttpURLConnection.HTTP_OK;
private String requestPath;
private Map requestHeaders = new HashMap(), requestParams = new HashMap();
private String contentType;

public void start() throws Exception {
// 0 is random available port
server = new Server(0);
server.setHandler(getMockHandler());
server.start();
}

public Handler getMockHandler() {
Handler handler = new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest httpServletRequest, HttpServletResponse response) throws IOException, ServletException {
requestPath = baseRequest.getUri().toString();
requestHeaders.clear();
Enumeration names = baseRequest.getHeaderNames();
while (names.hasMoreElements()) {
String headerName = (String) names.nextElement();
requestHeaders.put(headerName, baseRequest.getHeader(headerName));
}
requestParams.clear();
names = baseRequest.getParameterNames();
while (names.hasMoreElements()) {
String headerName = (String) names.nextElement();
requestParams.put(headerName, baseRequest.getParameter(headerName));
}
response.setStatus(responseStatus);
response.setContentType("application/json;charset=utf-8");
if (responseBody != null) {
write(responseBody, response.getOutputStream());
} else {
write(binaryResponseBody, response.getOutputStream());
}
baseRequest.setHandled(true);
}
};
return handler;
}

public void stop() {
try {
if (server != null) {
server.stop();
}
} catch (Exception e) {
throw new IllegalStateException("Fail to stop HTTP server", e);
}
}

public MockHttpServer doReturnBody(String responseBody) {
this.responseBody = responseBody;
return this;
}

public MockHttpServer doReturnBody(byte[] responseBody) {
this.binaryResponseBody = responseBody;
return this;
}

public MockHttpServer doReturnStatus(int status) {
this.responseStatus = status;
return this;
}

public MockHttpServer doReturnContentType(String contentType) {
this.contentType = contentType;
return this;
}

public String requestPath() {
return requestPath;
}

public Map requestHeaders() {
return requestHeaders;
}

public Map requestParams() {
return requestParams;
}

public int getPort() {
return server.getConnectors()[0].getLocalPort();
}
}

+ 60
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java Vedi File

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

import java.io.File;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonarqube.ws.MediaTypes;

import static org.assertj.core.api.Assertions.assertThat;

public class PostRequestTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

@Test
public void post_is_post() {
PostRequest request = new PostRequest("api/issues/search");
assertThat(request.getMethod()).isEqualTo(WsRequest.Method.POST);
}

@Test
public void empty_parts_and_params_by_default() {
PostRequest request = new PostRequest("api/issues/search");
assertThat(request.getParts()).isEmpty();
assertThat(request.getParams()).isEmpty();
}

@Test
public void add_part() throws IOException {
PostRequest request = new PostRequest("api/issues/search");
File reportFile = temp.newFile();
request.setPart("report", new PostRequest.Part(MediaTypes.JSON, reportFile));

assertThat(request.getParts()).hasSize(1);
PostRequest.Part part = request.getParts().get("report");
assertThat(part.getMediaType()).isEqualTo(MediaTypes.JSON);
assertThat(part.getFile()).isSameAs(reportFile);
}
}

+ 0
- 151
sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java Vedi File

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

package org.sonarqube.ws.client;

import com.google.common.net.HttpHeaders;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.WsComponents;

import static java.net.HttpURLConnection.HTTP_OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonarqube.ws.client.HttpConnector.newDefaultHttpConnector;
import static org.sonarqube.ws.client.HttpConnector.newHttpConnector;
import static org.sonarqube.ws.client.WsRequest.newGetRequest;

public class WsClientTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

MockHttpServer server;

WsClient underTest;

@Before
public void setUp() throws Exception {
server = new MockHttpServer();
server.start();

underTest = new WsClient(newDefaultHttpConnector("http://localhost:" + server.getPort()));
}

@After
public void stopServer() {
if (server != null) {
server.stop();
}
}

@Test
public void return_protobuf_response() throws Exception {
server.doReturnBody(
WsComponents.SearchWsResponse
.newBuilder()
.addComponents(WsComponents.SearchWsResponse.Component.getDefaultInstance())
.build()
.toByteArray());
server.doReturnStatus(HTTP_OK);
server.doReturnContentType(MediaTypes.PROTOBUF);

WsComponents.SearchWsResponse response = underTest.execute(
newGetRequest("api/components/search")
.setMediaType(WsRequest.MediaType.PROTOBUF),
WsComponents.SearchWsResponse.parser());

assertThat(response.getComponentsCount()).isEqualTo(1);
assertThat(server.requestHeaders().get(HttpHeaders.ACCEPT))
.isEqualTo(MediaTypes.PROTOBUF);
}

@Test
public void return_json_response() throws Exception {
String expectedResponse = "{\"key\":value}";
server.doReturnBody(expectedResponse);
server.doReturnStatus(HTTP_OK);
server.doReturnContentType(MediaTypes.JSON);

String response = underTest.execute(newGetRequest("api/components/search"));

assertThat(response).isEqualTo(expectedResponse);
assertThat(server.requestHeaders().get(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.JSON);
}

@Test
public void url_should_not_be_null() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Server URL must be set");

new WsClient(newHttpConnector().build());
}

@Test
public void url_should_not_be_empty() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Server URL must be set");

new WsClient(newDefaultHttpConnector(""));
}

@Test
public void test_default_configuration() throws Exception {
underTest = new WsClient(newDefaultHttpConnector("http://localhost:9000"));

HttpRequestFactory requestFactory = ((HttpConnector) underTest.wsConnector).requestFactory;
assertThat(requestFactory.getBaseUrl()).isEqualTo("http://localhost:9000");
assertThat(requestFactory.getLogin()).isNull();
assertThat(requestFactory.getPassword()).isNull();
assertThat(requestFactory.getConnectTimeoutInMilliseconds()).isEqualTo(HttpConnector.DEFAULT_CONNECT_TIMEOUT_MILLISECONDS);
assertThat(requestFactory.getReadTimeoutInMilliseconds()).isEqualTo(HttpConnector.DEFAULT_READ_TIMEOUT_MILLISECONDS);
assertThat(requestFactory.getProxyHost()).isNull();
assertThat(requestFactory.getProxyPort()).isEqualTo(0);
assertThat(requestFactory.getProxyLogin()).isNull();
assertThat(requestFactory.getProxyPassword()).isNull();
}

@Test
public void test_custom_configuration() throws Exception {
underTest = new WsClient(newHttpConnector()
.url("http://localhost:9000")
.login("eric")
.password("pass")
.connectTimeoutMilliseconds(12345)
.readTimeoutMilliseconds(6789)
.proxy("localhost", 2052)
.proxyLogin("proxyLogin")
.proxyPassword("proxyPass")
.build());

HttpRequestFactory requestFactory = ((HttpConnector) underTest.wsConnector).requestFactory;
assertThat(requestFactory.getBaseUrl()).isEqualTo("http://localhost:9000");
assertThat(requestFactory.getLogin()).isEqualTo("eric");
assertThat(requestFactory.getPassword()).isEqualTo("pass");
assertThat(requestFactory.getConnectTimeoutInMilliseconds()).isEqualTo(12345);
assertThat(requestFactory.getReadTimeoutInMilliseconds()).isEqualTo(6789);
assertThat(requestFactory.getProxyHost()).isEqualTo("localhost");
assertThat(requestFactory.getProxyPort()).isEqualTo(2052);
assertThat(requestFactory.getProxyLogin()).isEqualTo("proxyLogin");
assertThat(requestFactory.getProxyPassword()).isEqualTo("proxyPass");
}
}

+ 0
- 80
sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java Vedi File

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

package org.sonarqube.ws.client;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonarqube.ws.client.WsRequest.Method;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonarqube.ws.client.WsRequest.newGetRequest;
import static org.sonarqube.ws.client.WsRequest.newPostRequest;

public class WsRequestTest {

static final String ENDPOINT = "api/issues/search";

@Rule
public ExpectedException expectedException = ExpectedException.none();

WsRequest underTest;

@Test
public void get_request() {
underTest = newGetRequest("api/issues/search");

assertThat(underTest.getMethod()).isEqualTo(Method.GET);
}

@Test
public void post_request() {
underTest = newPostRequest("api/issues/search");

assertThat(underTest.getMethod()).isEqualTo(Method.POST);
}

@Test
public void set_non_null_param() {
underTest = newGetRequest("api/issues/search")
.setParam("key", "value");

assertThat(underTest.getParams().get("key")).isEqualTo("value");
}

@Test
public void set_null_param_remove_existing_param() {
underTest = newGetRequest(ENDPOINT)
.setParam("key", "value")
.setParam("key", null);

assertThat(underTest.getParams().get("key")).isNull();
}

@Test
public void fail_if_key_is_null() {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("a WS parameter key cannot be null");

underTest = newGetRequest(ENDPOINT)
.setParam(null, "value");
}
}

+ 29
- 0
sonar-ws/src/test/protobuf/ws-testing.proto Vedi File

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

syntax = "proto2";

package sonarqube.ws.testing;

option java_package = "org.sonarqube.ws";
option java_outer_classname = "Testing";
option optimize_for = SPEED;

message Fake {
optional string label = 1;
}

Loading…
Annulla
Salva