From bba16edb777a69137b3122dfbc5b898049f274c5 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 25 Nov 2015 10:54:56 +0100 Subject: [PATCH] SONAR-7054 use ws-client in batch --- .../java/it/actionPlan/ActionPlanTest.java | 4 +- .../it/authorisation/AuthenticationTest.java | 53 ++-- .../it/authorisation/IssuePermissionTest.java | 4 +- .../java/it/authorisation/PermissionTest.java | 24 +- .../test/java/it/issue/CommonRulesTest.java | 2 +- .../it/measureFilter/MeasureFiltersTest.java | 2 +- it/it-tests/src/test/java/util/ItUtils.java | 16 +- pom.xml | 15 +- .../java/org/sonar/server/batch/Messages.java | 3 +- .../server/permission/ws/PermissionsWs.java | 4 +- .../server/usertoken/ws/UserTokensWs.java | 4 +- .../batch/ProjectDataLoaderMediumTest.java | 2 +- .../permission/ws/AddGroupActionTest.java | 6 +- .../permission/ws/AddUserActionTest.java | 24 +- .../permission/ws/RemoveGroupActionTest.java | 6 +- .../permission/ws/RemoveUserActionTest.java | 24 +- sonar-batch-protocol/pom.xml | 10 +- sonar-batch/pom.xml | 5 - .../analysis/AnalysisWSLoaderProvider.java | 6 +- .../batch/bootstrap/BatchPluginInstaller.java | 22 +- .../batch/bootstrap/GlobalContainer.java | 2 +- .../sonar/batch/bootstrap/ServerClient.java | 168 ---------- .../bootstrap/WsClientLoggingInterceptor.java | 90 ++++++ .../batch/bootstrap/WsClientProvider.java | 63 ++++ .../batch/cache/StrategyWSLoaderProvider.java | 7 +- .../java/org/sonar/batch/cache/WSLoader.java | 59 ++-- .../sonar/batch/report/ReportPublisher.java | 216 +++++-------- .../DefaultProjectRepositoriesLoader.java | 5 +- .../DefaultQualityProfileLoader.java | 12 +- .../AnalysisWSLoaderProviderTest.java | 13 +- .../bootstrap/BatchPluginInstallerTest.java | 21 +- .../batch/bootstrap/ServerClientTest.java | 138 -------- .../WsClientLoggingInterceptorTest.java | 129 ++++++++ .../batch/bootstrap/WsClientProviderTest.java | 81 +++++ .../cache/DefaultProjectCacheStatusTest.java | 21 +- .../batch/cache/ProjectSyncContainerTest.java | 14 +- .../cache/StrategyWSLoaderProviderTest.java | 15 +- .../org/sonar/batch/cache/WSLoaderTest.java | 163 ++++------ .../batch/cache/WSLoaderWithServerTest.java | 111 ------- .../batch/report/ReportPublisherTest.java | 148 +++++---- .../DefaultProjectRepositoriesLoaderTest.java | 17 +- sonar-ws/pom.xml | 44 ++- .../java/org/sonarqube/ws/MediaTypes.java | 3 +- .../org/sonarqube/ws/client/BaseRequest.java | 75 +++++ .../org/sonarqube/ws/client/BaseService.java | 63 ++++ .../org/sonarqube/ws/client/GetRequest.java | 34 ++ .../sonarqube/ws/client/HttpConnector.java | 247 +++++++++++---- .../sonarqube/ws/client/HttpException.java | 17 +- .../ws/client/HttpRequestFactory.java | 229 -------------- .../org/sonarqube/ws/client/HttpResponse.java | 90 ++++++ .../org/sonarqube/ws/client/HttpWsClient.java | 89 ++++++ .../sonarqube/ws/client/MockWsResponse.java | 108 +++++++ .../org/sonarqube/ws/client/PostRequest.java} | 58 ++-- .../org/sonarqube/ws/client/WsClient.java | 74 +---- .../org/sonarqube/ws/client/WsConnector.java | 22 +- .../org/sonarqube/ws/client/WsRequest.java | 73 +---- .../org/sonarqube/ws/client/WsResponse.java | 41 +++ .../ws/client/ce/ComputeEngineService.java | 44 +++ .../ws/client/ce/SubmitWsRequest.java | 71 +++++ .../sonarqube/ws/client/ce/package-info.java | 25 ++ ...tsWsClient.java => ComponentsService.java} | 31 +- .../client/issue/IssueFilterParameters.java | 2 +- ...IssuesWsClient.java => IssuesService.java} | 20 +- ...sWsClient.java => PermissionsService.java} | 123 ++++---- .../permission/PermissionsWsParameters.java | 2 +- ...lient.java => QualityProfilesService.java} | 20 +- ...nsWsClient.java => UserTokensService.java} | 42 +-- .../usertoken/UserTokensWsParameters.java | 2 +- .../sonarqube/ws/client/BaseRequestTest.java | 84 +++++ .../sonarqube/ws/client/BaseServiceTest.java | 93 ++++++ .../ws/client/HttpConnectorTest.java | 297 ++++++++++++++++++ .../ws/client/HttpExceptionTest.java | 2 +- .../ws/client/HttpRequestFactoryTest.java | 86 ----- .../sonarqube/ws/client/MockHttpServer.java | 128 -------- .../sonarqube/ws/client/PostRequestTest.java | 60 ++++ .../org/sonarqube/ws/client/WsClientTest.java | 151 --------- .../sonarqube/ws/client/WsRequestTest.java | 80 ----- sonar-ws/src/test/protobuf/ws-testing.proto | 29 ++ 78 files changed, 2350 insertions(+), 2038 deletions(-) delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java delete mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java delete mode 100644 sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java delete mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java rename sonar-ws/src/{test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java => main/java/org/sonarqube/ws/client/PostRequest.java} (53%) create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java rename sonar-ws/src/main/java/org/sonarqube/ws/client/component/{ComponentsWsClient.java => ComponentsService.java} (60%) rename sonar-ws/src/main/java/org/sonarqube/ws/client/issue/{IssuesWsClient.java => IssuesService.java} (94%) rename sonar-ws/src/main/java/org/sonarqube/ws/client/permission/{PermissionsWsClient.java => PermissionsService.java} (66%) rename sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/{QualityProfilesWsClient.java => QualityProfilesService.java} (75%) rename sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/{UserTokensWsClient.java => UserTokensService.java} (55%) create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java delete mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java delete mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java delete mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java delete mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java create mode 100644 sonar-ws/src/test/protobuf/ws-testing.proto diff --git a/it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java b/it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java index 914ed9eb526..fe73d0dc13c 100644 --- a/it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java +++ b/it/it-tests/src/test/java/it/actionPlan/ActionPlanTest.java @@ -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) diff --git a/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java b/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java index 30d0bdd5043..7288287edbe 100644 --- a/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java +++ b/it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java @@ -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)); } diff --git a/it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java b/it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java index 5a45198f444..3eb384be9d1 100644 --- a/it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java +++ b/it/it-tests/src/test/java/it/authorisation/IssuePermissionTest.java @@ -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)); diff --git a/it/it-tests/src/test/java/it/authorisation/PermissionTest.java b/it/it-tests/src/test/java/it/authorisation/PermissionTest.java index e3dfe05fdb0..8c273061637 100644 --- a/it/it-tests/src/test/java/it/authorisation/PermissionTest.java +++ b/it/it-tests/src/test/java/it/authorisation/PermissionTest.java @@ -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)); } } diff --git a/it/it-tests/src/test/java/it/issue/CommonRulesTest.java b/it/it-tests/src/test/java/it/issue/CommonRulesTest.java index fcd14f5061f..6324365637f 100644 --- a/it/it-tests/src/test/java/it/issue/CommonRulesTest.java +++ b/it/it-tests/src/test/java/it/issue/CommonRulesTest.java @@ -86,7 +86,7 @@ public class CommonRulesTest extends AbstractIssueTest { } private List findIssues(String componentKey, String ruleKey) { - return adminWsClient.issuesWsClient().search( + return adminWsClient.issues().search( new SearchWsRequest() .setComponents(singletonList(componentKey)) .setRules(singletonList(ruleKey))) diff --git a/it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java b/it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java index 17f4fb9de52..4d0a3fdadff 100644 --- a/it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java +++ b/it/it-tests/src/test/java/it/measureFilter/MeasureFiltersTest.java @@ -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)); } diff --git a/it/it-tests/src/test/java/util/ItUtils.java b/it/it-tests/src/test/java/util/ItUtils.java index 0f293e3b4ce..6270b999363 100644 --- a/it/it-tests/src/test/java/util/ItUtils.java +++ b/it/it-tests/src/test/java/util/ItUtils.java @@ -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()); } diff --git a/pom.xml b/pom.xml index b1d4a957d20..598fbdbcc1d 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 1.7.12 8.0.18 1.7.2 + 2.6.0 3.0.0-beta-1 ${settings.localRepository}/com/google/protobuf/protoc/${protobuf.version}/protoc-${protobuf.version}-${os.detected.classifier}.exe @@ -1342,9 +1343,6 @@ src/main/protobuf - - target/generated-sources/protobuf/**/* - @@ -1352,7 +1350,7 @@ maven-antrun-plugin - protobuf-compile + compile-protobuf-sources generate-sources run @@ -1389,7 +1387,7 @@ build-helper-maven-plugin - protobuf-compile + add-protobuf-generated-sources generate-sources add-source @@ -1412,9 +1410,6 @@ src/test/protobuf - - target/generated-test-sources/protobuf/**/* - @@ -1422,7 +1417,7 @@ maven-antrun-plugin - protobuf-test-compile + compile-protobuf-tests generate-sources run @@ -1459,7 +1454,7 @@ build-helper-maven-plugin - protobuf-test-compile + add-protobuf-generated-tests generate-test-sources add-test-source diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java b/server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java index 40e05183577..f72f7544373 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/Messages.java @@ -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."; + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java index aa18be8aba4..2f3a6ba7ba8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWs.java @@ -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"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java index cc6aabc3679..0d6652a5498 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java @@ -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"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java index 8498f3fc3db..babb1c09362 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java @@ -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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java index ffce0b2aaaf..7e1b135f973 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java @@ -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() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java index fee6e5a2ae3..713be7d42a9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java @@ -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") diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java index 2bcf60caea9..5c7151701e5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java @@ -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) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java index a62a5a285ba..4128af2a6af 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java @@ -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") diff --git a/sonar-batch-protocol/pom.xml b/sonar-batch-protocol/pom.xml index e494d430f7b..5eb2081c161 100644 --- a/sonar-batch-protocol/pom.xml +++ b/sonar-batch-protocol/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.sonarsource.sonarqube @@ -9,12 +10,13 @@ sonar-batch-protocol SonarQube :: Batch :: Protocol - + Classes used for communication between batch and server - + - src/main/java/org/sonar/batch/protocol/viewer/** + target/generated-sources/**/*,src/main/java/org/sonar/batch/protocol/viewer/** + target/generated-test-sources/**/* diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml index e77766e5205..db6efc2bc68 100644 --- a/sonar-batch/pom.xml +++ b/sonar-batch/pom.xml @@ -100,10 +100,6 @@ com.google.code.gson gson - - com.github.kevinsawicki - http-request - org.freemarker @@ -118,7 +114,6 @@ test ${project.version} - com.google.code.bean-matchers bean-matchers diff --git a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java index ca0f521d42e..328478d3f87 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java @@ -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; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java index 88003026d8e..56b4199e8e6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -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); + } } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java index 035c7f489a9..40b5cacb9d1 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -91,7 +91,7 @@ public class GlobalContainer extends ComponentContainer { CachesManager.class, GlobalSettings.class, - ServerClient.class, + new WsClientProvider(), DefaultServer.class, new GlobalTempFolderProvider(), DefaultHttpDownloader.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java deleted file mode 100644 index ec34bfb7cef..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java +++ /dev/null @@ -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 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); - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java new file mode 100644 index 00000000000..f94bf084ee9 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java @@ -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 errorMessages = new ArrayList<>(); + for (JsonElement e : errors) { + errorMessages.add(e.getAsJsonObject().get("msg").getAsString()); + } + return Joiner.on(", ").join(errorMessages); + } catch (Exception e) { + return responseContent; + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java new file mode 100644 index 00000000000..78e50cc2ca2 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java @@ -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; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java index e64015db867..e7c573753df 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java @@ -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; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java index 13845f0808e..69e7c14538f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java @@ -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 stringServerLoader = new DataLoader() { @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 stringCacheLoader = new DataLoader() { @@ -83,13 +80,14 @@ public class WSLoader { private DataLoader streamServerLoader = new DataLoader() { @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 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()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java index 36c3324374b..209dd29712f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java @@ -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 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 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 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()); + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java index b31741f1115..3a00c304aaa 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java @@ -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; } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java index bd382543e52..8f6e0dd5e07 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java @@ -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 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 loadResource(String url, @Nullable MutableBoolean fromCache) { WSLoaderResult result = wsLoader.loadStream(url); if (fromCache != null) { @@ -85,7 +80,8 @@ public class DefaultQualityProfileLoader implements QualityProfileLoader { } List 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; } diff --git a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java index 7624edf7b28..5687ebdc27c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java @@ -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 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); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java index 654616811d5..fdc159569a0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -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("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 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(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java deleted file mode 100644 index ef7f4c58a1d..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ServerClientTest.java +++ /dev/null @@ -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("/")).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); - } - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java new file mode 100644 index 00000000000..4655371e88e --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java @@ -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 debugLogs = logTester.logs(LoggerLevel.DEBUG); + assertThat(debugLogs).hasSize(1); + assertThat(debugLogs.get(0)).contains("GET 200 https://localhost:9000/api/issues/search | time="); + List 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(); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java new file mode 100644 index 00000000000..ac5a9c86683 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java @@ -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()); + + 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 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()); + WsClient first = underTest.provide(settings, env); + WsClient second = underTest.provide(settings, env); + assertThat(first).isSameAs(second); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java index 8d86733a01d..831a0622c1d 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java @@ -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); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java index 15833a06ad5..560ce56a173 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java @@ -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()); ComponentContainer parent = new ComponentContainer(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java index 5d9ed445822..4134b7f6653 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java @@ -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); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java index c75259b5b36..2db585445f8 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java @@ -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() { - @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 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 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 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 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 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 { diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java deleted file mode 100644 index cd5b2e3b052..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderWithServerTest.java +++ /dev/null @@ -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); - } - -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java b/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java index 75b8af6dc51..d1c5e1a93c6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java @@ -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(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java index b8c9012399a..e0eebd486d3 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java @@ -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(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(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); diff --git a/sonar-ws/pom.xml b/sonar-ws/pom.xml index 3409a153d02..457bfa77dbb 100644 --- a/sonar-ws/pom.xml +++ b/sonar-ws/pom.xml @@ -13,20 +13,30 @@ SonarQube :: Web Service Protocol Buffers specification of Web Services + + target/generated-sources/**/* + target/generated-test-sources/**/* + + com.google.protobuf protobuf-java + + com.google.guava + guava + + + com.squareup.okhttp + okhttp + ${okhttp.version} + com.google.code.findbugs jsr305 provided - - com.google.guava - guava - commons-io commons-io @@ -36,32 +46,14 @@ ${project.groupId} sonar-testing-harness + ${project.version} test - javax.servlet - javax.servlet-api + com.squareup.okhttp + mockwebserver + ${okhttp.version} test - - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - test-jetty-servlet - test - - - com.googlecode.json-simple - json-simple - - - com.github.kevinsawicki - http-request - diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java b/sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java index ab3beddeca9..6947b77b59e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/MediaTypes.java @@ -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 MAP = new ImmutableMap.Builder() diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java new file mode 100644 index 00000000000..41c3339c92b --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java @@ -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 implements WsRequest { + + private final String path; + + private String mediaType = MediaTypes.JSON; + + // keep the same order -> do not use HashMap + private final Map 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 getParams() { + return params; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java new file mode 100644 index 00000000000..11d4d006419 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java @@ -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 call(BaseRequest request, Parser 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 convert(WsResponse response, Parser 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); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java new file mode 100644 index 00000000000..b59903fd563 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/GetRequest.java @@ -0,0 +1,34 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client; + +/** + * @since 5.3 + */ +public class GetRequest extends BaseRequest { + public GetRequest(String path) { + super(path); + } + + @Override + public Method getMethod() { + return Method.GET; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java index 66ac3cc89cd..7f46d08cacb 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java @@ -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 execute(WsRequest wsRequest, Parser 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 parts = postRequest.getParts(); + if (parts.isEmpty()) { + okRequestBuilder.post(RequestBody.create(null, "")); + } else { + MultipartBuilder body = new MultipartBuilder().type(MultipartBuilder.FORM); + for (Map.Entry 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 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 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); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java index 09aa68e5995..393c6232f89 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java @@ -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; } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java deleted file mode 100644 index cda4c549b74..00000000000 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpRequestFactory.java +++ /dev/null @@ -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 execute(WsRequest wsRequest, Parser 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; - } -} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java new file mode 100644 index 00000000000..b2e3f2766e4 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java @@ -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); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java new file mode 100644 index 00000000000..e9e69a7e8b7 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java @@ -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; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java new file mode 100644 index 00000000000..00411fb8601 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java @@ -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); + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java similarity index 53% rename from sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java index cd361262709..0c003894da2 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServerInterceptor.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java @@ -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 { - private MockHttpServer server; + private final Map 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 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(); - } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java index 39886b57a61..4b125627b5b 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java @@ -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. - *

- * Example: - *

- *   WsClient client = new WsClient(Connector);
- * 
- * - * @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 execute(WsRequest wsRequest, Parser 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(); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java index c4f282744ed..d7754d598fc 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java @@ -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 execute(WsRequest wsRequest, Parser 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(); + } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java index 9e4a91f13c4..dc635e963c7 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java @@ -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 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 getParams() { - return params; - } + Map getParams(); - public enum Method { + enum Method { GET, POST } - - public enum MediaType { - PROTOBUF, JSON, TEXT - } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java new file mode 100644 index 00000000000..c6550ffb44a --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java @@ -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(); +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java new file mode 100644 index 00000000000..465b96dc7b9 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java @@ -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()); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java new file mode 100644 index 00000000000..af414ee6fcc --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java @@ -0,0 +1,71 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.ws.client.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; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java new file mode 100644 index 00000000000..8b9789bcf15 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java @@ -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; + diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java similarity index 60% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsClient.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java index 1b081fe6ebe..fcee519bdb0 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java @@ -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()); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java index 8fe92d8820f..f28bc23033d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssueFilterParameters.java @@ -26,7 +26,7 @@ import com.google.common.collect.Iterables; import java.util.List; /** - * @since 3.7 + * @since 5.3 */ public class IssueFilterParameters { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java similarity index 94% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsClient.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java index 70708974382..343481ddbe4 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java @@ -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 strings) { return strings == null diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsService.java similarity index 66% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsClient.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsService.java index bb14ee02718..89f18f852ca 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsService.java @@ -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()); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java index 1557e97b632..edd9f6b3a4a 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/permission/PermissionsWsParameters.java @@ -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"; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java similarity index 75% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesWsClient.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java index dcbdec4951c..505ac3e8281 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java @@ -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; - } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensService.java similarity index 55% rename from sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java rename to sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensService.java index fdd7eeca721..ce9a39d2211 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensService.java @@ -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; - } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java index 203ee5243a8..a82d6e0989c 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java @@ -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"; diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java new file mode 100644 index 00000000000..42fcf4705d1 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseRequestTest.java @@ -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(String path) { + super(path); + } + + @Override + public Method getMethod() { + return Method.GET; + } + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java new file mode 100644 index 00000000000..2d92809cbd7 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java @@ -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()); + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java new file mode 100644 index 00000000000..79362ea815f --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java @@ -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!")); + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java index 3eab476fec9..2db49ea358b 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java @@ -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"); } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java deleted file mode 100644 index a256004a73c..00000000000 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpRequestFactoryTest.java +++ /dev/null @@ -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"); - } -} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java deleted file mode 100644 index c24be3ef64c..00000000000 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/MockHttpServer.java +++ /dev/null @@ -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(); - } -} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java new file mode 100644 index 00000000000..3693cdff9a8 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/PostRequestTest.java @@ -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); + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java deleted file mode 100644 index 13ff1fa14f3..00000000000 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsClientTest.java +++ /dev/null @@ -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"); - } -} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java deleted file mode 100644 index 28b5c04aede..00000000000 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/WsRequestTest.java +++ /dev/null @@ -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"); - } -} diff --git a/sonar-ws/src/test/protobuf/ws-testing.proto b/sonar-ws/src/test/protobuf/ws-testing.proto new file mode 100644 index 00000000000..9e802d9e1ca --- /dev/null +++ b/sonar-ws/src/test/protobuf/ws-testing.proto @@ -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; +} -- 2.39.5