@@ -36,6 +36,7 @@ | |||
<module>dashboard-plugin</module> | |||
<module>extension-lifecycle-plugin</module> | |||
<module>global-property-change-plugin</module> | |||
<module>issue-filter-plugin</module> | |||
<module>l10n-fr-pack</module> | |||
<module>license-plugin</module> | |||
<module>oauth2-auth-plugin</module> | |||
@@ -50,7 +51,7 @@ | |||
<module>sonar-fake-plugin</module> | |||
<module>sonar-subcategories-plugin</module> | |||
<module>ui-extensions-plugin</module> | |||
<module>issue-filter-plugin</module> | |||
<module>posttask-plugin</module> | |||
<module>ws-plugin</module> | |||
</modules> | |||
</project> |
@@ -0,0 +1,43 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.sonarsource.sonarqube</groupId> | |||
<artifactId>it-plugins</artifactId> | |||
<version>5.5-SNAPSHOT</version> | |||
</parent> | |||
<artifactId>ws-plugin</artifactId> | |||
<version>1.0-SNAPSHOT</version> | |||
<packaging>sonar-plugin</packaging> | |||
<name>SonarQube Integration Tests :: Plugins :: Ws</name> | |||
<description>Plugin for WS tests</description> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.sonarsource.sonarqube</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<version>${apiVersion}</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.sonarsource.sonarqube</groupId> | |||
<artifactId>sonar-ws</artifactId> | |||
<version>${apiVersion}</version> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> | |||
<artifactId>sonar-packaging-maven-plugin</artifactId> | |||
<extensions>true</extensions> | |||
<configuration> | |||
<pluginClass>WsPlugin</pluginClass> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,85 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.RequestHandler; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonarqube.ws.MediaTypes; | |||
import org.sonarqube.ws.WsCe; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.LocalWsClientFactory; | |||
import org.sonarqube.ws.client.WsClient; | |||
import org.sonarqube.ws.client.WsResponse; | |||
public final class LocalCallWebService implements WebService { | |||
private final LocalWsClientFactory wsClientFactory; | |||
public LocalCallWebService(LocalWsClientFactory wsClientFactory) { | |||
this.wsClientFactory = wsClientFactory; | |||
} | |||
@Override | |||
public void define(Context context) { | |||
NewController controller = context.createController("local_ws_call"); | |||
controller.createAction("protobuf_data").setHandler(new ProtobufHandler()); | |||
controller.createAction("json_data").setHandler(new JsonHandler()); | |||
controller.createAction("require_permission").setHandler(new RequirePermissionHandler()); | |||
controller.done(); | |||
} | |||
private class ProtobufHandler implements RequestHandler { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
WsClient client = wsClientFactory.newClient(request.getLocalConnector()); | |||
WsCe.TaskTypesWsResponse ceTaskTypes = client.ce().taskTypes(); | |||
response.stream().setStatus(ceTaskTypes.getTaskTypesCount() > 0 ? 200 : 500); | |||
} | |||
} | |||
private class JsonHandler implements RequestHandler { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
WsClient client = wsClientFactory.newClient(request.getLocalConnector()); | |||
WsResponse jsonResponse = client.wsConnector().call(new GetRequest("api/issues/search")); | |||
boolean ok = jsonResponse.contentType().equals(MediaTypes.JSON) | |||
&& jsonResponse.isSuccessful() | |||
&& jsonResponse.content().contains("\"issues\":"); | |||
response.stream().setStatus(ok ? 200 : 500); | |||
} | |||
} | |||
private class RequirePermissionHandler implements RequestHandler { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
WsClient client = wsClientFactory.newClient(request.getLocalConnector()); | |||
WsResponse jsonResponse = client.wsConnector().call(new GetRequest("api/system/info")); | |||
boolean ok = MediaTypes.JSON.equals(jsonResponse.contentType()) | |||
&& jsonResponse.isSuccessful() | |||
&& jsonResponse.content().startsWith("{"); | |||
response.stream().setStatus(ok ? 200 : 500); | |||
} | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.sonar.api.SonarPlugin; | |||
import org.sonarqube.ws.client.WsClientFactories; | |||
public class WsPlugin extends SonarPlugin { | |||
public List getExtensions() { | |||
return Arrays.asList(LocalCallWebService.class, WsClientFactories.getLocal()); | |||
} | |||
} |
@@ -41,6 +41,7 @@ import it.user.ForceAuthenticationTest; | |||
import it.user.LocalAuthenticationTest; | |||
import it.user.MyAccountPageTest; | |||
import it.user.OAuth2IdentityProviderTest; | |||
import it.ws.WsLocalCallTest; | |||
import org.junit.ClassRule; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Suite; | |||
@@ -83,7 +84,8 @@ import static util.ItUtils.xooPlugin; | |||
// ui | |||
UiTest.class, | |||
// ui extensions | |||
UiExtensionsTest.class | |||
UiExtensionsTest.class, | |||
WsLocalCallTest.class | |||
}) | |||
public class Category4Suite { | |||
@@ -104,5 +106,7 @@ public class Category4Suite { | |||
// Used in UiExtensionsTest | |||
.addPlugin(pluginArtifact("ui-extensions-plugin")) | |||
// Used by WsLocalCallTest | |||
.addPlugin(pluginArtifact("ws-plugin")) | |||
.build(); | |||
} |
@@ -37,8 +37,8 @@ import org.junit.experimental.categories.Category; | |||
import org.sonarqube.ws.WsUserTokens; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.HttpConnector; | |||
import org.sonarqube.ws.client.HttpWsClient; | |||
import org.sonarqube.ws.client.WsClient; | |||
import org.sonarqube.ws.client.WsClientFactories; | |||
import org.sonarqube.ws.client.WsResponse; | |||
import org.sonarqube.ws.client.permission.AddGroupWsRequest; | |||
import org.sonarqube.ws.client.permission.AddUserWsRequest; | |||
@@ -109,7 +109,7 @@ public class LocalAuthenticationTest { | |||
userRule.createUser(login, name, null, password); | |||
// authenticate | |||
WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build()); | |||
WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build()); | |||
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); | |||
assertThat(response.content()).isEqualTo("{\"valid\":true}"); | |||
} | |||
@@ -120,7 +120,7 @@ public class LocalAuthenticationTest { | |||
WsUserTokens.GenerateWsResponse generateWsResponse = userTokensWsClient.generate(new GenerateWsRequest() | |||
.setLogin(LOGIN) | |||
.setName(tokenName)); | |||
WsClient wsClient = new HttpWsClient(new HttpConnector.Builder() | |||
WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() | |||
.url(ORCHESTRATOR.getServer().getUrl()) | |||
.token(generateWsResponse.getToken()).build()); | |||
@@ -196,7 +196,7 @@ public class LocalAuthenticationTest { | |||
// This check is failing because signup doesn't refresh the users ES index ! | |||
// Will be fixed by SONAR-7308 | |||
// userRule.verifyUserExists("signuplogin", "SignUpName", null); | |||
// userRule.verifyUserExists("signuplogin", "SignUpName", null); | |||
} | |||
@Test | |||
@@ -208,14 +208,14 @@ public class LocalAuthenticationTest { | |||
"/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html", | |||
// SONAR-2009 | |||
"/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html" | |||
).build()).runOn(ORCHESTRATOR); | |||
).build()).runOn(ORCHESTRATOR); | |||
setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", "true"); | |||
new SeleneseTest(Selenese.builder().setHtmlTestsInClasspath("force-authentication", | |||
// SONAR-3473 | |||
"/user/LocalAuthenticationTest/force-authentication.html" | |||
).build()).runOn(ORCHESTRATOR); | |||
).build()).runOn(ORCHESTRATOR); | |||
} | |||
@Test |
@@ -0,0 +1,78 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package it.ws; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import it.Category4Suite; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import org.junit.experimental.categories.Category; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.HttpConnector; | |||
import org.sonarqube.ws.client.WsClient; | |||
import org.sonarqube.ws.client.WsClientFactories; | |||
import org.sonarqube.ws.client.WsResponse; | |||
import util.QaOnly; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
* Tests the ability for a web service to call another web services. | |||
*/ | |||
@Category(QaOnly.class) | |||
public class WsLocalCallTest { | |||
@ClassRule | |||
public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; | |||
@Test | |||
public void gets_protobuf() { | |||
WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/protobuf_data")); | |||
assertThat(response.isSuccessful()).isTrue(); | |||
} | |||
@Test | |||
public void gets_json() { | |||
WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/json_data")); | |||
assertThat(response.isSuccessful()).isTrue(); | |||
} | |||
@Test | |||
public void propagates_authorization_rights() { | |||
WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() | |||
.url(orchestrator.getServer().getUrl()) | |||
.credentials("admin", "admin") | |||
.build()); | |||
WsResponse response = wsClient.wsConnector().call(new GetRequest("local_ws_call/require_permission")); | |||
assertThat(response.isSuccessful()).isTrue(); | |||
} | |||
@Test | |||
public void fails_if_requires_permissions() { | |||
WsResponse response = newAnonymousClient().wsConnector().call(new GetRequest("local_ws_call/require_permission")); | |||
// this is not the unauthorized code as plugin forces it to 500 | |||
assertThat(response.code()).isEqualTo(500); | |||
} | |||
private static WsClient newAnonymousClient() { | |||
return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(orchestrator.getServer().getUrl()).build()); | |||
} | |||
} |
@@ -49,8 +49,8 @@ import org.sonar.wsclient.issue.IssueQuery; | |||
import org.sonar.wsclient.services.PropertyDeleteQuery; | |||
import org.sonar.wsclient.services.PropertyUpdateQuery; | |||
import org.sonarqube.ws.client.HttpConnector; | |||
import org.sonarqube.ws.client.HttpWsClient; | |||
import org.sonarqube.ws.client.WsClient; | |||
import org.sonarqube.ws.client.WsClientFactories; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static com.google.common.collect.FluentIterable.from; | |||
@@ -77,7 +77,7 @@ public class ItUtils { | |||
public static WsClient newAdminWsClient(Orchestrator orchestrator) { | |||
Server server = orchestrator.getServer(); | |||
return new HttpWsClient(new HttpConnector.Builder() | |||
return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() | |||
.url(server.getUrl()) | |||
.credentials(ADMIN_LOGIN, ADMIN_PASSWORD) | |||
.build()); | |||
@@ -85,7 +85,7 @@ public class ItUtils { | |||
public static WsClient newWsClient(Orchestrator orchestrator) { | |||
Server server = orchestrator.getServer(); | |||
return new HttpWsClient(new HttpConnector.Builder() | |||
return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() | |||
.url(server.getUrl()) | |||
.build()); | |||
} |
@@ -0,0 +1,146 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.ws; | |||
import com.google.common.base.Throwables; | |||
import com.google.common.collect.Maps; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.io.OutputStreamWriter; | |||
import java.net.HttpURLConnection; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import org.apache.commons.io.IOUtils; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.api.utils.text.XmlWriter; | |||
import org.sonarqube.ws.MediaTypes; | |||
public class DefaultLocalResponse implements Response, LocalConnector.LocalResponse { | |||
private final InMemoryStream stream = new InMemoryStream(); | |||
private final ByteArrayOutputStream output = new ByteArrayOutputStream(); | |||
private final Map<String, String> headers = Maps.newHashMap(); | |||
@Override | |||
public int getStatus() { | |||
return stream().status(); | |||
} | |||
@Override | |||
public String getMediaType() { | |||
return stream().mediaType(); | |||
} | |||
@Override | |||
public byte[] getBytes() { | |||
return output.toByteArray(); | |||
} | |||
public class InMemoryStream implements Response.Stream { | |||
private String mediaType; | |||
private int status = 200; | |||
@CheckForNull | |||
public String mediaType() { | |||
return mediaType; | |||
} | |||
public int status() { | |||
return status; | |||
} | |||
@Override | |||
public Response.Stream setMediaType(String s) { | |||
this.mediaType = s; | |||
return this; | |||
} | |||
@Override | |||
public Response.Stream setStatus(int i) { | |||
this.status = i; | |||
return this; | |||
} | |||
@Override | |||
public OutputStream output() { | |||
return output; | |||
} | |||
} | |||
@Override | |||
public JsonWriter newJsonWriter() { | |||
stream.setMediaType(MediaTypes.JSON); | |||
return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8)); | |||
} | |||
@Override | |||
public XmlWriter newXmlWriter() { | |||
stream.setMediaType(MediaTypes.XML); | |||
return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8)); | |||
} | |||
@Override | |||
public InMemoryStream stream() { | |||
return stream; | |||
} | |||
@Override | |||
public Response noContent() { | |||
stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT); | |||
IOUtils.closeQuietly(output); | |||
return this; | |||
} | |||
public String outputAsString() { | |||
return new String(output.toByteArray(), StandardCharsets.UTF_8); | |||
} | |||
@Override | |||
public Response setHeader(String name, String value) { | |||
headers.put(name, value); | |||
return this; | |||
} | |||
@Override | |||
public Collection<String> getHeaderNames() { | |||
return headers.keySet(); | |||
} | |||
@Override | |||
public String getHeader(String name) { | |||
return headers.get(name); | |||
} | |||
public byte[] getFlushedOutput() { | |||
try { | |||
output.flush(); | |||
return output.toByteArray(); | |||
} catch (IOException e) { | |||
throw Throwables.propagate(e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.ws; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.InputStream; | |||
import java.nio.charset.StandardCharsets; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonar.api.server.ws.internal.ValidatingRequest; | |||
public class LocalRequestAdapter extends ValidatingRequest { | |||
private final LocalConnector.LocalRequest localRequest; | |||
public LocalRequestAdapter(LocalConnector.LocalRequest localRequest) { | |||
this.localRequest = localRequest; | |||
} | |||
@Override | |||
protected String readParam(String key) { | |||
return localRequest.getParam(key); | |||
} | |||
@Override | |||
protected InputStream readInputStreamParam(String key) { | |||
String value = readParam(key); | |||
if (value == null) { | |||
return null; | |||
} | |||
return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)); | |||
} | |||
@Override | |||
public boolean hasParam(String key) { | |||
return localRequest.hasParam(key); | |||
} | |||
@Override | |||
public String method() { | |||
return localRequest.getMethod(); | |||
} | |||
@Override | |||
public String getMediaType() { | |||
return localRequest.getMediaType(); | |||
} | |||
} |
@@ -22,9 +22,13 @@ package org.sonar.server.ws; | |||
import java.io.OutputStreamWriter; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.List; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.internal.ValidatingRequest; | |||
import org.sonar.api.utils.log.Loggers; | |||
@@ -43,7 +47,7 @@ import static org.sonar.server.ws.RequestVerifier.verifyRequest; | |||
* @since 4.2 | |||
*/ | |||
@ServerSide | |||
public class WebServiceEngine implements Startable { | |||
public class WebServiceEngine implements LocalConnector, Startable { | |||
private final WebService.Context context; | |||
private final I18n i18n; | |||
@@ -76,11 +80,22 @@ public class WebServiceEngine implements Startable { | |||
return context.controllers(); | |||
} | |||
public void execute(ValidatingRequest request, ServletResponse response, | |||
String controllerPath, String actionKey) { | |||
@Override | |||
public LocalResponse call(LocalRequest request) { | |||
String controller = StringUtils.substringBeforeLast(request.getPath(), "/"); | |||
String action = StringUtils.substringAfterLast(request.getPath(), "/"); | |||
DefaultLocalResponse localResponse = new DefaultLocalResponse(); | |||
execute(new LocalRequestAdapter(request), localResponse, controller, action); | |||
return localResponse; | |||
} | |||
public void execute(Request request, Response response, String controllerPath, String actionKey) { | |||
try { | |||
WebService.Action action = getAction(controllerPath, actionKey); | |||
request.setAction(action); | |||
if (request instanceof ValidatingRequest) { | |||
((ValidatingRequest) request).setAction(action); | |||
((ValidatingRequest) request).setLocalConnector(this); | |||
} | |||
verifyRequest(action, request); | |||
action.handler().handle(request, response); | |||
@@ -112,9 +127,11 @@ public class WebServiceEngine implements Startable { | |||
return action; | |||
} | |||
private void sendErrors(ServletResponse response, int status, Errors errors) { | |||
ServletResponse.ServletStream stream = response.stream(); | |||
stream.reset(); | |||
private void sendErrors(Response response, int status, Errors errors) { | |||
Response.Stream stream = response.stream(); | |||
if (stream instanceof ServletResponse.ServletStream) { | |||
((ServletResponse.ServletStream) stream).reset(); | |||
} | |||
stream.setStatus(status); | |||
stream.setMediaType(MediaTypes.JSON); | |||
JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8)); |
@@ -0,0 +1,113 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.api.server.ws; | |||
import com.google.common.annotations.Beta; | |||
import java.util.Collection; | |||
import javax.annotation.CheckForNull; | |||
/** | |||
* This class allows a web service to call another web service through the sonar-ws library. | |||
* The call is in-process, synchronous and does not involve the HTTP stack. | |||
* <p> | |||
* Example of a web service that uses sonar-ws 5.5 to get some issues: | |||
* <pre> | |||
* import org.sonar.api.server.ws.RequestHandler; | |||
* import org.sonarqube.ws.client.WsClientFactories; | |||
* | |||
* public class MyRequestHandler implements RequestHandler { | |||
* {@literal @}Override | |||
* public void handle(Request request, Response response) { | |||
* WsClient wsClient = WsClientFactories.getLocal().newClient(request.getLocalConnector()); | |||
* SearchWsResponse issues = wsClient.issues().search(new SearchWsRequest()); | |||
* // ... | |||
* } | |||
* } | |||
* </pre> | |||
* </p> | |||
* @since 5.5 | |||
*/ | |||
@Beta | |||
public interface LocalConnector { | |||
LocalResponse call(LocalRequest request); | |||
interface LocalRequest { | |||
/** | |||
* URL path, which is the concatenation of controller path and action key, for example "api/issues/search" | |||
* @see org.sonar.api.server.ws.WebService.Controller#path | |||
* @see org.sonar.api.server.ws.WebService.Action#key | |||
*/ | |||
String getPath(); | |||
/** | |||
* @see Request#getMediaType() | |||
*/ | |||
String getMediaType(); | |||
/** | |||
* HTTP method. Possible values are "GET" and "POST" | |||
* @see Request#method() | |||
*/ | |||
String getMethod(); | |||
/** | |||
* @see Request#hasParam(String) | |||
*/ | |||
boolean hasParam(String key); | |||
/** | |||
* @see Request#param(String) | |||
*/ | |||
@CheckForNull | |||
String getParam(String key); | |||
} | |||
interface LocalResponse { | |||
/** | |||
* @see org.sonar.api.server.ws.Response.Stream#setStatus(int) | |||
*/ | |||
int getStatus(); | |||
/** | |||
* @see org.sonar.api.server.ws.Response.Stream#setMediaType(String) | |||
*/ | |||
String getMediaType(); | |||
/** | |||
* Response body | |||
*/ | |||
byte[] getBytes(); | |||
/** | |||
* HTTP headers | |||
* @see Response#setHeader(String, String) | |||
*/ | |||
Collection<String> getHeaderNames(); | |||
/** | |||
* @see Response#setHeader(String, String) | |||
* @return | |||
*/ | |||
@CheckForNull | |||
String getHeader(String name); | |||
} | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.api.server.ws; | |||
import com.google.common.annotations.Beta; | |||
import com.google.common.base.Splitter; | |||
import com.google.common.collect.Lists; | |||
import java.io.InputStream; | |||
@@ -234,4 +235,12 @@ public abstract class Request { | |||
} | |||
throw new IllegalArgumentException(String.format("Property %s is not a boolean value: %s", key, value)); | |||
} | |||
/** | |||
* Used by the sonar-ws library to allow a web service to call another web service. | |||
* @see LocalConnector | |||
* @since 5.5 | |||
*/ | |||
@Beta | |||
public abstract LocalConnector getLocalConnector(); | |||
} |
@@ -23,7 +23,9 @@ import org.sonar.api.ExtensionPoint; | |||
import org.sonar.api.server.ServerSide; | |||
/** | |||
* Extension point to execute a HTTP request. | |||
* @since 4.2 | |||
* @see WebService | |||
*/ | |||
@ServerSide | |||
@ExtensionPoint |
@@ -34,6 +34,11 @@ public interface Response { | |||
interface Stream { | |||
Stream setMediaType(String s); | |||
/** | |||
* HTTP status code. See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes. | |||
* By default value is set to 200. | |||
*/ | |||
Stream setStatus(int httpStatus); | |||
OutputStream output(); | |||
} |
@@ -87,28 +87,6 @@ import static java.lang.String.format; | |||
* } | |||
* } | |||
* </pre> | |||
* <h3>How to test</h3> | |||
* <pre> | |||
* public class HelloWsTest { | |||
* WebService ws = new HelloWs(); | |||
* | |||
* {@literal @}Test | |||
* public void should_define_ws() throws Exception { | |||
* // WsTester is available in the Maven artifact org.codehaus.sonar:sonar-testing-harness | |||
* WsTester tester = new WsTester(ws); | |||
* WebService.Controller controller = tester.controller("api/hello"); | |||
* assertThat(controller).isNotNull(); | |||
* assertThat(controller.path()).isEqualTo("api/hello"); | |||
* assertThat(controller.description()).isNotEmpty(); | |||
* assertThat(controller.actions()).hasSize(1); | |||
* | |||
* WebService.Action show = controller.action("show"); | |||
* assertThat(show).isNotNull(); | |||
* assertThat(show.key()).isEqualTo("show"); | |||
* assertThat(index.handler()).isNotNull(); | |||
* } | |||
* } | |||
* </pre> | |||
* | |||
* @since 4.2 | |||
*/ |
@@ -24,6 +24,7 @@ import java.io.InputStream; | |||
import java.util.Map; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.IOUtils; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonar.api.server.ws.Request; | |||
import static com.google.common.base.Preconditions.checkNotNull; | |||
@@ -76,4 +77,12 @@ public class SimpleGetRequest extends Request { | |||
return this; | |||
} | |||
public Map<String, String> getParams() { | |||
return params; | |||
} | |||
@Override | |||
public LocalConnector getLocalConnector() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -29,16 +29,20 @@ import java.util.Set; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.log.Loggers; | |||
import static com.google.common.base.Preconditions.checkNotNull; | |||
/** | |||
* @since 4.2 | |||
*/ | |||
public abstract class ValidatingRequest extends Request { | |||
private WebService.Action action; | |||
private LocalConnector localConnector; | |||
public void setAction(WebService.Action action) { | |||
this.action = action; | |||
@@ -48,6 +52,16 @@ public abstract class ValidatingRequest extends Request { | |||
return action; | |||
} | |||
@Override | |||
public LocalConnector getLocalConnector() { | |||
checkNotNull(localConnector, "Local connector has not been set"); | |||
return localConnector; | |||
} | |||
public void setLocalConnector(LocalConnector lc) { | |||
this.localConnector = lc; | |||
} | |||
@Override | |||
@CheckForNull | |||
public String param(String key) { |
@@ -24,7 +24,7 @@ import org.sonar.api.CoreProperties; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.batch.bootstrapper.EnvironmentInformation; | |||
import org.sonarqube.ws.client.HttpConnector; | |||
import org.sonarqube.ws.client.HttpWsClient; | |||
import org.sonarqube.ws.client.WsClientFactories; | |||
import static java.lang.Integer.parseInt; | |||
import static java.lang.String.valueOf; | |||
@@ -42,20 +42,20 @@ public class BatchWsClientProvider extends ProviderAdapter { | |||
public synchronized BatchWsClient provide(final GlobalProperties settings, final EnvironmentInformation env) { | |||
if (wsClient == null) { | |||
String url = defaultIfBlank(settings.property("sonar.host.url"), CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE); | |||
HttpConnector.Builder builder = new HttpConnector.Builder(); | |||
HttpConnector.Builder connectorBuilder = HttpConnector.newBuilder(); | |||
// TODO proxy | |||
String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC)); | |||
String login = defaultIfBlank(settings.property(CoreProperties.LOGIN), null); | |||
builder | |||
connectorBuilder | |||
.readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000) | |||
.connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS) | |||
.userAgent(env.toString()) | |||
.url(url) | |||
.credentials(login, settings.property(CoreProperties.PASSWORD)); | |||
wsClient = new BatchWsClient(new HttpWsClient(builder.build()), login != null); | |||
wsClient = new BatchWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null); | |||
} | |||
return wsClient; | |||
} |
@@ -39,6 +39,12 @@ | |||
<groupId>commons-io</groupId> | |||
<artifactId>commons-io</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.sonarsource.sonarqube</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<scope>provided</scope> | |||
<optional>true</optional> | |||
</dependency> | |||
<!-- Tests --> | |||
<dependency> |
@@ -19,6 +19,8 @@ | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
abstract class BaseResponse implements WsResponse { | |||
@Override | |||
@@ -34,4 +36,8 @@ abstract class BaseResponse implements WsResponse { | |||
return this; | |||
} | |||
@Override | |||
public boolean hasContent() { | |||
return code() != HTTP_NO_CONTENT; | |||
} | |||
} |
@@ -26,6 +26,7 @@ import java.io.InputStream; | |||
import java.util.List; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.IOUtils; | |||
import org.sonarqube.ws.MediaTypes; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
@@ -56,8 +57,9 @@ public abstract class BaseService { | |||
public <T extends Message> T convert(WsResponse response, Parser<T> parser) { | |||
try (InputStream byteStream = response.contentStream()) { | |||
byte[] bytes = IOUtils.toByteArray(byteStream); | |||
// HTTP header "Content-Type" is not verified. It may be different than protobuf. | |||
return parser.parseFrom(byteStream); | |||
return parser.parseFrom(bytes); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to parse protobuf response of " + response.requestUrl(), e); | |||
} |
@@ -31,11 +31,12 @@ import org.sonarqube.ws.client.system.SystemService; | |||
import org.sonarqube.ws.client.usertoken.UserTokensService; | |||
/** | |||
* Entry point of the Java Client for SonarQube Web Services | |||
* This class is not public anymore since version 5.5. It is | |||
* created by {@link WsClientFactory} | |||
* | |||
* @since 5.3 | |||
*/ | |||
public class HttpWsClient implements WsClient { | |||
class DefaultWsClient implements WsClient { | |||
private final WsConnector wsConnector; | |||
private final PermissionsService permissionsService; | |||
@@ -49,7 +50,7 @@ public class HttpWsClient implements WsClient { | |||
private final CeService ceService; | |||
private final RulesService rulesService; | |||
public HttpWsClient(WsConnector wsConnector) { | |||
DefaultWsClient(WsConnector wsConnector) { | |||
this.wsConnector = wsConnector; | |||
this.permissionsService = new PermissionsService(wsConnector); | |||
this.componentsService = new ComponentsService(wsConnector); |
@@ -202,16 +202,23 @@ public class HttpConnector implements WsConnector { | |||
return okHttpRequestBuilder; | |||
} | |||
private HttpResponse doCall(Request okRequest) { | |||
private OkHttpResponse doCall(Request okRequest) { | |||
Call call = okHttpClient.newCall(okRequest); | |||
try { | |||
Response okResponse = call.execute(); | |||
return new HttpResponse(okResponse); | |||
return new OkHttpResponse(okResponse); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Fail to request " + okRequest.urlString(), e); | |||
} | |||
} | |||
/** | |||
* @since 5.5 | |||
*/ | |||
public static Builder newBuilder() { | |||
return new Builder(); | |||
} | |||
public static class Builder { | |||
private String url; | |||
private String userAgent; | |||
@@ -223,6 +230,13 @@ public class HttpConnector implements WsConnector { | |||
private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS; | |||
private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS; | |||
/** | |||
* Private since 5.5. | |||
* @see HttpConnector#newBuilder() | |||
*/ | |||
private Builder() { | |||
} | |||
/** | |||
* Optional User Agent | |||
*/ |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
@ServerSide | |||
public interface LocalWsClientFactory extends WsClientFactory { | |||
WsClient newClient(LocalConnector localConnector); | |||
} |
@@ -0,0 +1,132 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.io.Reader; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
class LocalWsConnector implements WsConnector { | |||
private final LocalConnector localConnector; | |||
LocalWsConnector(LocalConnector localConnector) { | |||
this.localConnector = localConnector; | |||
} | |||
@VisibleForTesting | |||
LocalConnector getLocalConnector() { | |||
return localConnector; | |||
} | |||
@Override | |||
public String baseUrl() { | |||
return "/"; | |||
} | |||
@Override | |||
public WsResponse call(WsRequest wsRequest) { | |||
DefaultLocalRequest localRequest = new DefaultLocalRequest(wsRequest); | |||
LocalConnector.LocalResponse localResponse = localConnector.call(localRequest); | |||
return new ByteArrayResponse(wsRequest.getPath(), localResponse); | |||
} | |||
private static class DefaultLocalRequest implements LocalConnector.LocalRequest { | |||
private final WsRequest wsRequest; | |||
public DefaultLocalRequest(WsRequest wsRequest) { | |||
this.wsRequest = wsRequest; | |||
} | |||
@Override | |||
public String getPath() { | |||
return wsRequest.getPath(); | |||
} | |||
@Override | |||
public String getMediaType() { | |||
return wsRequest.getMediaType(); | |||
} | |||
@Override | |||
public String getMethod() { | |||
return wsRequest.getMethod().name(); | |||
} | |||
@Override | |||
public boolean hasParam(String key) { | |||
return wsRequest.getParams().containsKey(key); | |||
} | |||
@Override | |||
public String getParam(String key) { | |||
return wsRequest.getParams().get(key); | |||
} | |||
} | |||
private static class ByteArrayResponse extends BaseResponse { | |||
private final String path; | |||
private final byte[] bytes; | |||
private final String contentType; | |||
private final int code; | |||
ByteArrayResponse(String path, LocalConnector.LocalResponse localResponse) { | |||
this.path = path; | |||
this.bytes = localResponse.getBytes(); | |||
this.contentType = localResponse.getMediaType(); | |||
this.code = localResponse.getStatus(); | |||
} | |||
@Override | |||
public String requestUrl() { | |||
return path; | |||
} | |||
@Override | |||
public int code() { | |||
return code; | |||
} | |||
@Override | |||
public String contentType() { | |||
return contentType; | |||
} | |||
@Override | |||
public InputStream contentStream() { | |||
return new ByteArrayInputStream(bytes); | |||
} | |||
@Override | |||
public Reader contentReader() { | |||
return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8); | |||
} | |||
@Override | |||
public String content() { | |||
return new String(bytes, UTF_8); | |||
} | |||
} | |||
} |
@@ -24,13 +24,11 @@ import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Reader; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
class HttpResponse extends BaseResponse { | |||
class OkHttpResponse extends BaseResponse { | |||
private final Response okResponse; | |||
HttpResponse(Response okResponse) { | |||
OkHttpResponse(Response okResponse) { | |||
this.okResponse = okResponse; | |||
} | |||
@@ -44,11 +42,6 @@ class HttpResponse extends BaseResponse { | |||
return okResponse.request().urlString(); | |||
} | |||
@Override | |||
public boolean hasContent() { | |||
return okResponse.code() != HTTP_NO_CONTENT; | |||
} | |||
@Override | |||
public String contentType() { | |||
return okResponse.header("Content-Type"); |
@@ -31,6 +31,21 @@ import org.sonarqube.ws.client.system.SystemService; | |||
import org.sonarqube.ws.client.usertoken.UserTokensService; | |||
/** | |||
* Allows to request the web services of SonarQube server. Instance is provided by | |||
* {@link WsClientFactory}. | |||
* | |||
* <p> | |||
* Usage: | |||
* <pre> | |||
* HttpConnector httpConnector = HttpConnector.newBuilder() | |||
* .url("http://localhost:9000") | |||
* .credentials("admin", "admin") | |||
* .build(); | |||
* WsClient wsClient = WsClientFactories.getDefault().newClient(httpConnector); | |||
* wsClient.issues().search(issueRequest); | |||
* </pre> | |||
* </p> | |||
* | |||
* @since 5.3 | |||
*/ | |||
public interface WsClient { |
@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
/** | |||
* All provided implementations of {@link WsClientFactory}. | |||
*/ | |||
public class WsClientFactories { | |||
private WsClientFactories() { | |||
// prevent instantiation | |||
} | |||
/** | |||
* Factory to be used when connecting to a remote SonarQube web server. | |||
*/ | |||
public static WsClientFactory getDefault() { | |||
return DefaultWsClientFactory.INSTANCE; | |||
} | |||
/** | |||
* Factory that allows a SonarQube web service to interact | |||
* with other web services, without using the HTTP stack. | |||
* @see org.sonar.api.server.ws.LocalConnector | |||
*/ | |||
public static LocalWsClientFactory getLocal() { | |||
return DefaultLocalWsClientFactory.INSTANCE; | |||
} | |||
private enum DefaultWsClientFactory implements WsClientFactory { | |||
INSTANCE; | |||
@Override | |||
public WsClient newClient(WsConnector connector) { | |||
return new DefaultWsClient(connector); | |||
} | |||
} | |||
private enum DefaultLocalWsClientFactory implements LocalWsClientFactory { | |||
INSTANCE; | |||
@Override | |||
public WsClient newClient(WsConnector connector) { | |||
return DefaultWsClientFactory.INSTANCE.newClient(connector); | |||
} | |||
@Override | |||
public WsClient newClient(LocalConnector localConnector) { | |||
return new DefaultWsClient(new LocalWsConnector(localConnector)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
/** | |||
* Creates {@link WsClient}. Implementations are provided by {@link WsClientFactories}. | |||
* | |||
* @since 5.5 | |||
*/ | |||
public interface WsClientFactory { | |||
WsClient newClient(WsConnector connector); | |||
} |
@@ -60,7 +60,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void test_default_settings() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); | |||
assertThat(underTest.baseUrl()).isEqualTo(serverUrl); | |||
GetRequest request = new GetRequest("api/issues/search").setMediaType(MediaTypes.PROTOBUF); | |||
WsResponse response = underTest.call(request); | |||
@@ -87,7 +87,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void use_basic_authentication() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.credentials("theLogin", "thePassword") | |||
.build(); | |||
@@ -102,7 +102,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void use_basic_authentication_with_null_password() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.credentials("theLogin", null) | |||
.build(); | |||
@@ -121,7 +121,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void use_access_token() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.token("theToken") | |||
.build(); | |||
@@ -136,7 +136,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void use_proxy_authentication() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.proxyCredentials("theProxyLogin", "theProxyPassword") | |||
.build(); | |||
@@ -150,7 +150,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void override_timeouts() { | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.readTimeoutMilliseconds(42) | |||
.connectTimeoutMilliseconds(74) | |||
@@ -163,7 +163,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void send_user_agent() throws Exception { | |||
answerHelloWorld(); | |||
HttpConnector underTest = new HttpConnector.Builder() | |||
HttpConnector underTest = HttpConnector.newBuilder() | |||
.url(serverUrl) | |||
.userAgent("Maven Plugin/2.3") | |||
.build(); | |||
@@ -176,7 +176,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void fail_if_unknown_implementation_of_request() { | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); | |||
try { | |||
underTest.call(mock(WsRequest.class)); | |||
fail(); | |||
@@ -192,7 +192,7 @@ public class HttpConnectorTest { | |||
.setParam("severity", "MAJOR") | |||
.setMediaType(MediaTypes.PROTOBUF); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); | |||
WsResponse response = underTest.call(request); | |||
// verify response | |||
@@ -215,7 +215,7 @@ public class HttpConnectorTest { | |||
.setPart("report", new PostRequest.Part(MediaTypes.TXT, reportFile)) | |||
.setMediaType(MediaTypes.PROTOBUF); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); | |||
WsResponse response = underTest.call(request); | |||
assertThat(response.hasContent()).isTrue(); | |||
@@ -233,7 +233,7 @@ public class HttpConnectorTest { | |||
public void http_error() throws Exception { | |||
server.enqueue(new MockResponse().setResponseCode(404)); | |||
PostRequest request = new PostRequest("api/issues/search"); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(); | |||
WsResponse wsResponse = underTest.call(request); | |||
assertThat(wsResponse.code()).isEqualTo(404); | |||
@@ -242,7 +242,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void support_base_url_ending_with_slash() throws Exception { | |||
assertThat(serverUrl).endsWith("/"); | |||
HttpConnector underTest = new HttpConnector.Builder().url(StringUtils.removeEnd(serverUrl, "/")).build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(StringUtils.removeEnd(serverUrl, "/")).build(); | |||
GetRequest request = new GetRequest("api/issues/search"); | |||
answerHelloWorld(); | |||
@@ -255,7 +255,7 @@ public class HttpConnectorTest { | |||
public void support_base_url_with_context() { | |||
// just to be sure | |||
assertThat(serverUrl).endsWith("/"); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl + "sonar").build(); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl + "sonar").build(); | |||
GetRequest request = new GetRequest("api/issues/search"); | |||
answerHelloWorld(); | |||
@@ -269,7 +269,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void support_tls_1_2_on_java7() { | |||
when(javaVersion.isJava7()).thenReturn(true); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion); | |||
assertTlsAndClearTextSpecifications(underTest); | |||
// enable TLS 1.0, 1.1 and 1.2 | |||
@@ -279,7 +279,7 @@ public class HttpConnectorTest { | |||
@Test | |||
public void support_tls_versions_of_java8() { | |||
when(javaVersion.isJava7()).thenReturn(false); | |||
HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build(javaVersion); | |||
HttpConnector underTest = HttpConnector.newBuilder().url(serverUrl).build(javaVersion); | |||
assertTlsAndClearTextSpecifications(underTest); | |||
assertThat(underTest.okHttpClient().getSslSocketFactory()).isInstanceOf(SSLSocketFactory.getDefault().getClass()); |
@@ -0,0 +1,150 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.apache.commons.io.IOUtils; | |||
import org.hamcrest.Description; | |||
import org.hamcrest.TypeSafeMatcher; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import org.sonarqube.ws.MediaTypes; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Matchers.argThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
public class LocalWsConnectorTest { | |||
LocalConnector connector = mock(LocalConnector.class); | |||
LocalWsConnector underTest = new LocalWsConnector(connector); | |||
@Test | |||
public void baseUrl_is_always_slash() { | |||
assertThat(underTest.baseUrl()).isEqualTo("/"); | |||
} | |||
@Test | |||
public void call_request() throws Exception { | |||
WsRequest wsRequest = new PostRequest("api/issues/search") | |||
.setMediaType(MediaTypes.JSON) | |||
.setParam("foo", "bar"); | |||
answer(new DumbLocalResponse(400, MediaTypes.JSON, "{}".getBytes(UTF_8), Collections.<String>emptyList())); | |||
WsResponse wsResponse = underTest.call(wsRequest); | |||
verifyRequested("POST", "api/issues/search", MediaTypes.JSON, ImmutableMap.of("foo", "bar")); | |||
assertThat(wsResponse.code()).isEqualTo(400); | |||
assertThat(wsResponse.content()).isEqualTo("{}"); | |||
assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo("{}"); | |||
assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo("{}"); | |||
assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON); | |||
assertThat(wsResponse.requestUrl()).isEqualTo("api/issues/search"); | |||
} | |||
@Test | |||
public void call_request_with_defaults() throws Exception { | |||
// no parameters, no media type | |||
WsRequest wsRequest = new GetRequest("api/issues/search"); | |||
answer(new DumbLocalResponse(200, MediaTypes.JSON, "".getBytes(UTF_8), Collections.<String>emptyList())); | |||
WsResponse wsResponse = underTest.call(wsRequest); | |||
verifyRequested("GET", "api/issues/search", MediaTypes.JSON, Collections.<String, String>emptyMap()); | |||
assertThat(wsResponse.code()).isEqualTo(200); | |||
assertThat(wsResponse.content()).isEqualTo(""); | |||
assertThat(IOUtils.toString(wsResponse.contentReader())).isEqualTo(""); | |||
assertThat(IOUtils.toString(wsResponse.contentStream())).isEqualTo(""); | |||
assertThat(wsResponse.contentType()).isEqualTo(MediaTypes.JSON); | |||
} | |||
private void answer(DumbLocalResponse response) { | |||
when(connector.call(any(LocalConnector.LocalRequest.class))).thenReturn(response); | |||
} | |||
private void verifyRequested(final String expectedMethod, final String expectedPath, | |||
final String expectedMediaType, | |||
final Map<String, String> expectedParams) { | |||
verify(connector).call(argThat(new TypeSafeMatcher<LocalConnector.LocalRequest>() { | |||
@Override | |||
protected boolean matchesSafely(LocalConnector.LocalRequest localRequest) { | |||
boolean ok = localRequest.getMethod().equals(expectedMethod) && localRequest.getPath().equals(expectedPath); | |||
ok &= localRequest.getMediaType().equals(expectedMediaType); | |||
for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) { | |||
String paramKey = expectedParam.getKey(); | |||
ok &= localRequest.hasParam(paramKey); | |||
ok &= expectedParam.getValue().equals(localRequest.getParam(paramKey)); | |||
} | |||
return ok; | |||
} | |||
@Override | |||
public void describeTo(Description description) { | |||
} | |||
})); | |||
} | |||
private static class DumbLocalResponse implements LocalConnector.LocalResponse { | |||
private final int code; | |||
private final String mediaType; | |||
private final byte[] bytes; | |||
private final List<String> headers; | |||
public DumbLocalResponse(int code, String mediaType, byte[] bytes, List<String> headers) { | |||
this.code = code; | |||
this.mediaType = mediaType; | |||
this.bytes = bytes; | |||
this.headers = headers; | |||
} | |||
@Override | |||
public int getStatus() { | |||
return code; | |||
} | |||
@Override | |||
public String getMediaType() { | |||
return mediaType; | |||
} | |||
@Override | |||
public byte[] getBytes() { | |||
return bytes; | |||
} | |||
@Override | |||
public Collection<String> getHeaderNames() { | |||
return headers; | |||
} | |||
@Override | |||
public String getHeader(String name) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.ws.client; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.LocalConnector; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
public class WsClientFactoriesTest { | |||
@Test | |||
public void create_http_client() { | |||
HttpConnector connector = HttpConnector.newBuilder().url("http://localhost:9000").build(); | |||
WsClient client = WsClientFactories.getDefault().newClient(connector); | |||
assertThat(client).isInstanceOf(DefaultWsClient.class); | |||
assertThat(client.wsConnector()).isSameAs(connector); | |||
} | |||
@Test | |||
public void create_local_client() { | |||
LocalConnector connector = mock(LocalConnector.class); | |||
WsClient client = WsClientFactories.getLocal().newClient(connector); | |||
assertThat(client).isInstanceOf(DefaultWsClient.class); | |||
assertThat(client.wsConnector()).isInstanceOf(LocalWsConnector.class); | |||
assertThat(((LocalWsConnector) client.wsConnector()).getLocalConnector()).isSameAs(connector); | |||
} | |||
} |