compile project(':server:sonar-db-migration')
compile project(':server:sonar-process')
compile project(':server:sonar-server-common')
+ compile project(':server:sonar-webserver-ws')
compile project(':sonar-core')
compile project(':sonar-duplications')
compile project(':sonar-scanner-protocol')
testCompile project(':server:sonar-db-testing')
testCompile project(path: ":server:sonar-server-common", configuration: "tests")
testCompile project(':sonar-testing-harness')
+ testCompile project(path: ":server:sonar-webserver-ws", configuration: "tests")
runtime 'io.jsonwebtoken:jjwt-jackson'
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import com.google.common.base.MoreObjects;
-import java.util.List;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
-import static java.util.Arrays.asList;
-
-/**
- * Request is not valid and can not be processed.
- */
-public class BadRequestException extends ServerException {
-
- private final transient List<String> errors;
-
- private BadRequestException(List<String> errors) {
- super(HTTP_BAD_REQUEST, errors.get(0));
- this.errors = errors;
- }
-
- public static BadRequestException create(List<String> errorMessages) {
- checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
- checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
- return new BadRequestException(errorMessages);
- }
-
- public static BadRequestException create(String... errorMessages) {
- return create(asList(errorMessages));
- }
-
- public List<String> errors() {
- return errors;
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("errors", errors)
- .toString();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import com.google.common.base.Preconditions;
-
-import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
-
-/**
- * Permission denied. User does not have the required permissions.
- */
-public class ForbiddenException extends ServerException {
-
- public ForbiddenException(String message) {
- super(HTTP_FORBIDDEN, Preconditions.checkNotNull(message));
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import com.google.common.base.Preconditions;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.lang.String.format;
-
-public class Message {
-
- private final String msg;
-
- private Message(String format, Object... params) {
- Preconditions.checkArgument(!isNullOrEmpty(format), "Message cannot be empty");
- this.msg = format(format, params);
- }
-
- public String getMessage() {
- return msg;
- }
-
- public static Message of(String msg, Object... arguments) {
- return new Message(msg, arguments);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Message other = (Message) o;
- return this.msg.equals(other.msg);
- }
-
- @Override
- public int hashCode() {
- return msg.hashCode();
- }
-
- @Override
- public String toString() {
- return msg;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
-
-public class NotFoundException extends ServerException {
-
- public NotFoundException(String message) {
- super(HTTP_NOT_FOUND, message);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import static java.util.Objects.requireNonNull;
-
-public class ServerException extends RuntimeException {
- private final int httpCode;
-
- public ServerException(int httpCode, String message) {
- super(requireNonNull(message, "Error message cannot be null"));
- this.httpCode = httpCode;
- }
-
- public int httpCode() {
- return httpCode;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.exceptions;
-
-import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
-
-/**
- * User needs to be authenticated. HTTP request is generally redirected to login form.
- */
-public class UnauthorizedException extends ServerException {
-
- public UnauthorizedException(String message) {
- super(HTTP_UNAUTHORIZED, message);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.exceptions;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl;
import org.sonar.server.settings.ProjectConfigurationLoaderImpl;
import org.sonar.server.webhook.WebhookQGChangeEventListener;
-import org.sonar.server.ws.WsResponseCommonFormat;
public class IssueWsModule extends Module {
@Override
SearchResponseLoader.class,
SearchResponseFormat.class,
OperationResponseWriter.class,
- WsResponseCommonFormat.class,
AddCommentAction.class,
EditCommentAction.class,
DeleteCommentAction.class,
import org.sonar.markdown.Markdown;
import org.sonar.server.es.Facets;
import org.sonar.server.issue.workflow.Transition;
-import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Issues.Actions;
public class SearchResponseFormat {
private final Durations durations;
- private final WsResponseCommonFormat commonFormat;
private final Languages languages;
private final AvatarResolver avatarFactory;
- public SearchResponseFormat(Durations durations, WsResponseCommonFormat commonFormat, Languages languages, AvatarResolver avatarFactory) {
+ public SearchResponseFormat(Durations durations, Languages languages, AvatarResolver avatarFactory) {
this.durations = durations;
- this.commonFormat = commonFormat;
this.languages = languages;
this.avatarFactory = avatarFactory;
}
response.setP(paging.pageIndex());
response.setPs(paging.pageSize());
response.setTotal(paging.total());
- response.setPaging(commonFormat.formatPaging(paging));
+ response.setPaging(formatPaging(paging));
+ }
+
+ private Common.Paging.Builder formatPaging(Paging paging) {
+ return Common.Paging.newBuilder()
+ .setPageIndex(paging.pageIndex())
+ .setPageSize(paging.pageSize())
+ .setTotal(paging.total());
}
private List<Issues.Issue> formatIssues(Set<SearchAdditionalField> fields, SearchResponseData data) {
Common.Rules.Builder wsRules = Common.Rules.newBuilder();
List<RuleDefinitionDto> rules = firstNonNull(data.getRules(), emptyList());
for (RuleDefinitionDto rule : rules) {
- wsRules.addRules(commonFormat.formatRule(rule));
+ wsRules.addRules(formatRule(rule));
}
return wsRules;
}
+ private Common.Rule.Builder formatRule(RuleDefinitionDto rule) {
+ Common.Rule.Builder builder = Common.Rule.newBuilder()
+ .setKey(rule.getKey().toString())
+ .setName(nullToEmpty(rule.getName()))
+ .setStatus(Common.RuleStatus.valueOf(rule.getStatus().name()));
+
+ builder.setLang(nullToEmpty(rule.getLanguage()));
+ Language lang = languages.get(rule.getLanguage());
+ if (lang != null) {
+ builder.setLangName(lang.getName());
+ }
+ return builder;
+ }
+
private static List<Issues.Component> formatComponents(SearchResponseData data) {
Collection<ComponentDto> components = data.getComponents();
List<Issues.Component> result = new ArrayList<>();
import org.sonar.server.qualityprofile.QualityProfile;
import org.sonar.server.ui.PageRepository;
import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.WsUtils;
import static java.lang.String.format;
import static java.util.Collections.emptySortedSet;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
-import static org.sonar.server.ws.WsUtils.checkComponentNotAModuleAndNotADirectory;
public class ComponentAction implements NavigationWsAction {
+ private static final Set<String> MODULE_OR_DIR_QUALIFIERS = ImmutableSet.of(Qualifiers.MODULE, Qualifiers.DIRECTORY);
static final String PARAM_COMPONENT = "component";
private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";
}
}
+ private static void checkComponentNotAModuleAndNotADirectory(ComponentDto component) {
+ WsUtils.checkRequest(!MODULE_OR_DIR_QUALIFIERS.contains(component.qualifier()), "Operation not supported for module or directory components");
+ }
+
private ComponentDto getRootProjectOrBranch(ComponentDto component, DbSession session) {
if (!component.isRootProject()) {
return dbClient.componentDao().selectOrFailByUuid(session, component.projectUuid());
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-import org.apache.commons.io.IOUtils;
-
-/**
- * Writer that writes only when closing the resource
- */
-class CacheWriter extends Writer {
- private final StringWriter bufferWriter;
- private final Writer outputWriter;
- private boolean isClosed;
-
- CacheWriter(Writer outputWriter) {
- this.bufferWriter = new StringWriter();
- this.outputWriter = outputWriter;
- this.isClosed = false;
- }
-
- @Override
- public void write(char[] cbuf, int off, int len) {
- bufferWriter.write(cbuf, off, len);
- }
-
- @Override
- public void flush() {
- bufferWriter.flush();
- }
-
- @Override
- public void close() throws IOException {
- if (isClosed) {
- return;
- }
-
- IOUtils.write(bufferWriter.toString(), outputWriter);
- outputWriter.close();
- this.isClosed = true;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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);
- }
- }
-}
import org.apache.commons.io.IOUtils;
import org.sonar.api.web.ServletFilter;
import org.sonar.server.property.ws.IndexAction;
+import org.sonar.server.setting.ws.SettingsWsParameters;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.sonar.server.property.ws.PropertiesWs.CONTROLLER_PROPERTIES;
-import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT;
-import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEY;
-import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEYS;
-import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUE;
-import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUES;
/**
* This filter is used to execute deprecated api/properties WS, that were using REST
}
@Override
- protected String readParam(String key) {
+ public String readParam(String key) {
return restResponse.additionalParams.get(key);
}
@Override
- protected List<String> readMultiParam(String key) {
+ public List<String> readMultiParam(String key) {
return new ArrayList<>(restResponse.additionalMultiParams.get(key));
}
}
private void redirectToSet(Optional<String> key, List<String> values, Optional<String> component) {
- addParameterIfPresent(PARAM_KEY, key);
+ addParameterIfPresent(SettingsWsParameters.PARAM_KEY, key);
if (values.size() == 1) {
- additionalParams.put(PARAM_VALUE, values.get(0));
+ additionalParams.put(SettingsWsParameters.PARAM_VALUE, values.get(0));
} else {
- additionalMultiParams.putAll(PARAM_VALUES, values);
+ additionalMultiParams.putAll(SettingsWsParameters.PARAM_VALUES, values);
}
- addParameterIfPresent(PARAM_COMPONENT, component);
+ addParameterIfPresent(SettingsWsParameters.PARAM_COMPONENT, component);
redirectedPath = "api/settings/set";
redirectedMethod = "POST";
}
private void redirectToReset(Optional<String> key, Optional<String> component) {
- addParameterIfPresent(PARAM_KEYS, key);
- addParameterIfPresent(PARAM_COMPONENT, component);
+ addParameterIfPresent(SettingsWsParameters.PARAM_KEYS, key);
+ addParameterIfPresent(SettingsWsParameters.PARAM_COMPONENT, component);
redirectedPath = "api/settings/reset";
redirectedMethod = "POST";
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.util.Collection;
-import java.util.Date;
-import javax.annotation.Nullable;
-import org.sonar.api.utils.text.JsonWriter;
-
-public class JsonWriterUtils {
-
- private JsonWriterUtils() {
- // Utility class
- }
-
- public static void writeIfNeeded(JsonWriter json, @Nullable String value, String field, @Nullable Collection<String> fields) {
- if (isFieldNeeded(field, fields)) {
- json.prop(field, value);
- }
- }
-
- public static void writeIfNeeded(JsonWriter json, @Nullable Boolean value, String field, @Nullable Collection<String> fields) {
- if (isFieldNeeded(field, fields)) {
- json.prop(field, value);
- }
- }
-
- public static void writeIfNeeded(JsonWriter json, @Nullable Integer value, String field, @Nullable Collection<String> fields) {
- if (isFieldNeeded(field, fields)) {
- json.prop(field, value);
- }
- }
-
- public static void writeIfNeeded(JsonWriter json, @Nullable Long value, String field, @Nullable Collection<String> fields) {
- if (isFieldNeeded(field, fields)) {
- json.prop(field, value);
- }
- }
-
- public static void writeIfNeeded(JsonWriter json, @Nullable Date value, String field, @Nullable Collection<String> fields) {
- if (isFieldNeeded(field, fields)) {
- json.propDateTime(field, value);
- }
- }
-
- public static boolean isFieldNeeded(String field, @Nullable Collection<String> fields) {
- return fields == null || fields.isEmpty() || fields.contains(field);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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;
-
-public class KeyExamples {
- public static final String KEY_FILE_EXAMPLE_001 = "my_project:/src/foo/Bar.php";
- public static final String KEY_FILE_EXAMPLE_002 = "another_project:/src/foo/Foo.php";
- public static final String KEY_PROJECT_EXAMPLE_001 = "my_project";
- public static final String KEY_PROJECT_EXAMPLE_002 = "another_project";
- public static final String KEY_PROJECT_EXAMPLE_003 = "third_project";
-
- public static final String KEY_ORG_EXAMPLE_001 = "my-org";
- public static final String KEY_ORG_EXAMPLE_002 = "foo-company";
-
- public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch";
- public static final String KEY_PULL_REQUEST_EXAMPLE_001 = "5461";
-
- public static final String NAME_WEBHOOK_EXAMPLE_001 = "My Webhook";
- public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar";
-
- private KeyExamples() {
- // prevent instantiation
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import org.sonar.api.server.ws.LocalConnector;
-import org.sonar.api.impl.ws.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
- public Map<String, String[]> getParams() {
- return localRequest.getParameterMap();
- }
-
- @Override
- protected List<String> readMultiParam(String key) {
- return localRequest.getMultiParam(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
- protected Part readPart(String key) {
- throw new UnsupportedOperationException("reading part is not supported yet by local WS calls");
- }
-
- @Override
- public boolean hasParam(String key) {
- return localRequest.hasParam(key);
- }
-
- @Override
- public String getPath() {
- return localRequest.getPath();
- }
-
- @Override
- public String method() {
- return localRequest.getMethod();
- }
-
- @Override
- public String getMediaType() {
- return localRequest.getMediaType();
- }
-
- @Override
- public Optional<String> header(String name) {
- return localRequest.getHeader(name);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.io.Resources;
-import java.net.URL;
-import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.RequestHandler;
-import org.sonar.api.server.ws.Response;
-import org.sonar.server.exceptions.ServerException;
-
-import static java.net.HttpURLConnection.HTTP_GONE;
-
-/**
- * Used to declare web services that are removed
- */
-public enum RemovedWebServiceHandler implements RequestHandler {
-
- INSTANCE;
-
- @Override
- public void handle(Request request, Response response) {
- throw new ServerException(HTTP_GONE, String.format("The web service '%s' doesn't exist anymore, please read its documentation to use alternatives", request.getPath()));
- }
-
- public URL getResponseExample() {
- return Resources.getResource(RemovedWebServiceHandler.class, "removed-ws-example.json");
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.server.exceptions.ServerException;
-
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-
-public class RequestVerifier {
- private RequestVerifier() {
- // static methods only
- }
-
- public static void verifyRequest(WebService.Action action, Request request) {
- switch (request.method()) {
- case "GET":
- if (action.isPost()) {
- throw new ServerException(SC_METHOD_NOT_ALLOWED, "HTTP method POST is required");
- }
- return;
- case "PUT":
- case "DELETE":
- throw new ServerException(SC_METHOD_NOT_ALLOWED, String.format("HTTP method %s is not allowed", request.method()));
- default:
- // Nothing to do
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.RequestHandler;
-import org.sonar.api.server.ws.Response;
-
-/**
- * Used to declare web services that are implemented by a servlet filter.
- */
-public class ServletFilterHandler implements RequestHandler {
-
- public static final RequestHandler INSTANCE = new ServletFilterHandler();
-
- private ServletFilterHandler() {
- // Nothing
- }
-
- @Override
- public void handle(Request request, Response response) {
- throw new UnsupportedOperationException("This web service is implemented as a servlet filter");
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.net.HttpHeaders;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.CheckForNull;
-import javax.servlet.http.HttpServletRequest;
-import org.sonar.api.impl.ws.PartImpl;
-import org.sonar.api.impl.ws.ValidatingRequest;
-import org.sonar.api.utils.log.Loggers;
-import org.sonarqube.ws.MediaTypes;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static java.util.Collections.emptyList;
-import static java.util.Locale.ENGLISH;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-import static org.apache.tomcat.util.http.fileupload.FileUploadBase.MULTIPART;
-
-public class ServletRequest extends ValidatingRequest {
-
- private final HttpServletRequest source;
-
- static final Map<String, String> SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX = ImmutableMap.of(
- "json", MediaTypes.JSON,
- "protobuf", MediaTypes.PROTOBUF,
- "text", MediaTypes.TXT);
-
- public ServletRequest(HttpServletRequest source) {
- this.source = source;
- }
-
- @Override
- public String method() {
- return source.getMethod();
- }
-
- @Override
- public String getMediaType() {
- return firstNonNull(
- mediaTypeFromUrl(source.getRequestURI()),
- firstNonNull(
- acceptedContentTypeInResponse(),
- MediaTypes.DEFAULT));
- }
-
- @Override
- public BufferedReader getReader() {
- try {
- return source.getReader();
- } catch (IOException e) {
- throw new IllegalStateException("Failed to read", e);
- }
- }
-
- @Override
- public boolean hasParam(String key) {
- return source.getParameterMap().containsKey(key);
- }
-
- @Override
- protected String readParam(String key) {
- return source.getParameter(key);
- }
-
- @Override
- public Map<String, String[]> getParams() {
- return source.getParameterMap();
- }
-
- @Override
- protected List<String> readMultiParam(String key) {
- String[] values = source.getParameterValues(key);
- return values == null ? emptyList() : ImmutableList.copyOf(values);
- }
-
- @Override
- protected InputStream readInputStreamParam(String key) {
- Part part = readPart(key);
- return (part == null) ? null : part.getInputStream();
- }
-
- @Override
- @CheckForNull
- public Part readPart(String key) {
- try {
- if (!isMultipartContent()) {
- return null;
- }
- javax.servlet.http.Part part = source.getPart(key);
- if (part == null || part.getSize() == 0) {
- return null;
- }
- return new PartImpl(part.getInputStream(), part.getSubmittedFileName());
- } catch (Exception e) {
- Loggers.get(ServletRequest.class).warn("Can't read file part for parameter " + key, e);
- return null;
- }
- }
-
- private boolean isMultipartContent() {
- String contentType = source.getContentType();
- return contentType != null && contentType.toLowerCase(ENGLISH).startsWith(MULTIPART);
- }
-
- @Override
- public String toString() {
- StringBuffer url = source.getRequestURL();
- String query = source.getQueryString();
- if (query != null) {
- url.append("?").append(query);
- }
- return url.toString();
- }
-
- @CheckForNull
- private String acceptedContentTypeInResponse() {
- return source.getHeader(HttpHeaders.ACCEPT);
- }
-
- @CheckForNull
- private static String mediaTypeFromUrl(String url) {
- String formatSuffix = substringAfterLast(url, ".");
- return SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX.get(formatSuffix.toLowerCase(ENGLISH));
- }
-
- @Override
- public String getPath() {
- return source.getRequestURI().replaceFirst(source.getContextPath(), "");
- }
-
- @Override
- public Optional<String> header(String name) {
- return Optional.ofNullable(source.getHeader(name));
- }
-
- @Override
- public Map<String, String> getHeaders() {
- ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
- Enumeration<String> headerNames = source.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String headerName = headerNames.nextElement();
- mapBuilder.put(headerName, source.getHeader(headerName));
- }
- return mapBuilder.build();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.api.utils.text.XmlWriter;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.sonarqube.ws.MediaTypes.JSON;
-import static org.sonarqube.ws.MediaTypes.XML;
-
-public class ServletResponse implements Response {
-
- private final ServletStream stream;
-
- public ServletResponse(HttpServletResponse response) {
- stream = new ServletStream(response);
- }
-
- public static class ServletStream implements Stream {
- private final HttpServletResponse response;
-
- public ServletStream(HttpServletResponse response) {
- this.response = response;
- this.response.setStatus(200);
- // SONAR-6964 WS should not be cached by browser
- this.response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
- }
-
- @Override
- public ServletStream setMediaType(String s) {
- this.response.setContentType(s);
- return this;
- }
-
- @Override
- public ServletStream setStatus(int httpStatus) {
- this.response.setStatus(httpStatus);
- return this;
- }
-
- @Override
- public OutputStream output() {
- try {
- return response.getOutputStream();
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
- HttpServletResponse response() {
- return response;
- }
-
- public ServletStream reset() {
- response.reset();
- return this;
- }
- }
-
- @Override
- public JsonWriter newJsonWriter() {
- stream.setMediaType(JSON);
- return JsonWriter.of(new CacheWriter(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8)));
- }
-
- @Override
- public XmlWriter newXmlWriter() {
- stream.setMediaType(XML);
- return XmlWriter.of(new OutputStreamWriter(stream.output(), UTF_8));
- }
-
- @Override
- public ServletStream stream() {
- return stream;
- }
-
- @Override
- public Response noContent() {
- stream.setStatus(204);
- return this;
- }
-
- @Override
- public Response setHeader(String name, String value) {
- stream.response().setHeader(name, value);
- return this;
- }
-
- @Override
- public Collection<String> getHeaderNames() {
- return stream.response().getHeaderNames();
- }
-
- @Override
- public String getHeader(String name) {
- return stream.response().getHeader(name);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Locale;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.catalina.connector.ClientAbortException;
-import org.picocontainer.Startable;
-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.impl.ws.ValidatingRequest;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.ServerException;
-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.Collections.singletonList;
-import static java.util.Objects.requireNonNull;
-import static org.apache.commons.lang.StringUtils.substring;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-import static org.apache.commons.lang.StringUtils.substringBeforeLast;
-import static org.sonar.server.ws.RequestVerifier.verifyRequest;
-import static org.sonar.server.ws.ServletRequest.SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX;
-import static org.sonar.server.ws.WsUtils.checkFound;
-
-/**
- * @since 4.2
- */
-@ServerSide
-public class WebServiceEngine implements LocalConnector, Startable {
-
- private static final Logger LOGGER = Loggers.get(WebServiceEngine.class);
-
- private final WebService[] webServices;
-
- private WebService.Context context;
-
- public WebServiceEngine(WebService[] webServices) {
- this.webServices = webServices;
- }
-
- @Override
- public void start() {
- context = new WebService.Context();
- for (WebService webService : webServices) {
- webService.define(context);
- }
- }
-
- @Override
- public void stop() {
- // nothing
- }
-
- private WebService.Context getContext() {
- return requireNonNull(context, "Web services has not yet been initialized");
- }
-
- List<WebService.Controller> controllers() {
- return getContext().controllers();
- }
-
- @Override
- public LocalResponse call(LocalRequest request) {
- DefaultLocalResponse localResponse = new DefaultLocalResponse();
- execute(new LocalRequestAdapter(request), localResponse);
- return localResponse;
- }
-
- public void execute(Request request, Response response) {
- try {
- ActionExtractor actionExtractor = new ActionExtractor(request.getPath());
- WebService.Action action = getAction(actionExtractor);
- checkFound(action, "Unknown url : %s", request.getPath());
- if (request instanceof ValidatingRequest) {
- ((ValidatingRequest) request).setAction(action);
- ((ValidatingRequest) request).setLocalConnector(this);
- }
- checkActionExtension(actionExtractor.getExtension());
- verifyRequest(action, request);
- action.handler().handle(request, response);
- } catch (IllegalArgumentException e) {
- sendErrors(request, response, e, 400, singletonList(e.getMessage()));
- } catch (BadRequestException e) {
- sendErrors(request, response, e, 400, e.errors());
- } catch (ServerException e) {
- sendErrors(request, response, e, e.httpCode(), singletonList(e.getMessage()));
- } catch (Exception e) {
- sendErrors(request, response, e, 500, singletonList("An error has occurred. Please contact your administrator"));
- }
- }
-
- @CheckForNull
- private WebService.Action getAction(ActionExtractor actionExtractor) {
- String controllerPath = actionExtractor.getController();
- String actionKey = actionExtractor.getAction();
- WebService.Controller controller = getContext().controller(controllerPath);
- return controller == null ? null : controller.action(actionKey);
- }
-
- private static void sendErrors(Request request, Response response, Exception exception, int status, List<String> errors) {
- if (isRequestAbortedByClient(exception)) {
- // do not pollute logs. We can't do anything -> use DEBUG level
- // see org.sonar.server.ws.ServletResponse#output()
- LOGGER.debug(String.format("Request %s has been aborted by client", request), exception);
- if (!isResponseCommitted(response)) {
- // can be useful for access.log
- response.stream().setStatus(299);
- }
- return;
- }
-
- if (status == 500) {
- // Sending exception message into response is a vulnerability. Error must be
- // displayed only in logs.
- LOGGER.error("Fail to process request " + request, exception);
- }
-
- Response.Stream stream = response.stream();
- if (isResponseCommitted(response)) {
- // status can't be changed
- LOGGER.debug(String.format("Request %s failed during response streaming", request), exception);
- return;
- }
-
- // response is not committed, status and content can be changed to return the error
- if (stream instanceof ServletResponse.ServletStream) {
- ((ServletResponse.ServletStream) stream).reset();
- }
- stream.setStatus(status);
- stream.setMediaType(MediaTypes.JSON);
- try (JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8))) {
- json.beginObject();
- writeErrors(json, errors);
- json.endObject();
- } catch (Exception e) {
- // Do not hide the potential exception raised in the try block.
- throw Throwables.propagate(e);
- }
- }
-
- private static boolean isRequestAbortedByClient(Exception exception) {
- return Throwables.getCausalChain(exception).stream().anyMatch(t -> t instanceof ClientAbortException);
- }
-
- private static boolean isResponseCommitted(Response response) {
- Response.Stream stream = response.stream();
- // Request has been aborted by the client or the response was partially streamed, nothing can been done as Tomcat has committed the response
- return stream instanceof ServletResponse.ServletStream && ((ServletResponse.ServletStream) stream).response().isCommitted();
- }
-
- public static void writeErrors(JsonWriter json, List<String> errorMessages) {
- if (errorMessages.isEmpty()) {
- return;
- }
- json.name("errors").beginArray();
- errorMessages.forEach(message -> {
- json.beginObject();
- json.prop("msg", message);
- json.endObject();
- });
- json.endArray();
- }
-
- private static void checkActionExtension(@Nullable String actionExtension) {
- if (isNullOrEmpty(actionExtension)) {
- return;
- }
- checkArgument(SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX.get(actionExtension.toLowerCase(Locale.ENGLISH)) != null, "Unknown action extension: %s", actionExtension);
- }
-
- private static class ActionExtractor {
- private static final String SLASH = "/";
- private static final String POINT = ".";
-
- private final String controller;
- private final String action;
- private final String extension;
- private final String path;
-
- ActionExtractor(String path) {
- this.path = path;
- String pathWithoutExtension = substringBeforeLast(path, POINT);
- this.controller = extractController(pathWithoutExtension);
- this.action = substringAfterLast(pathWithoutExtension, SLASH);
- checkArgument(!action.isEmpty(), "Url is incorrect : '%s'", path);
- this.extension = substringAfterLast(path, POINT);
- }
-
- private static String extractController(String path) {
- String controller = substringBeforeLast(path, SLASH);
- if (controller.startsWith(SLASH)) {
- return substring(controller, 1);
- }
- return controller;
- }
-
- String getController() {
- return controller;
- }
-
- String getAction() {
- return action;
- }
-
- @CheckForNull
- String getExtension() {
- return extension;
- }
-
- String getPath() {
- return path;
- }
- }
-
-}
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.ServletFilter;
import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.property.ws.PropertiesWs;
import static java.util.stream.Stream.concat;
-import static org.sonar.server.property.ws.PropertiesWs.CONTROLLER_PROPERTIES;
import static org.sonar.server.ws.WebServiceReroutingFilter.MOVED_WEB_SERVICES;
/**
.map(toPath()))
.collect(MoreCollectors.toSet());
this.excludeUrls = concat(concat(
- Stream.of("/" + CONTROLLER_PROPERTIES + "*"),
+ Stream.of("/" + PropertiesWs.CONTROLLER_PROPERTIES + "*"),
MOVED_WEB_SERVICES.stream()),
webServiceEngine.controllers().stream()
.flatMap(controller -> controller.actions().stream())
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 org.sonar.api.server.ws.Definable;
-import org.sonar.api.server.ws.RequestHandler;
-import org.sonar.api.server.ws.WebService;
-
-/**
- * Since 5.2, this interface is the base for Web Service marker interfaces
- * Convention for naming implementations: <i>web_service_class_name</i>Action. ex: ProjectsWsAction, UsersWsAction
- */
-public interface WsAction extends RequestHandler, Definable<WebService.NewController> {
- // Marker interface
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.util.Locale;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-import org.sonar.api.resources.ResourceType;
-import org.sonar.api.resources.ResourceTypes;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.core.i18n.I18n;
-
-import static java.lang.String.format;
-
-public class WsParameterBuilder {
- private static final String PARAM_QUALIFIER = "qualifier";
- private static final String PARAM_QUALIFIERS = "qualifiers";
-
- private WsParameterBuilder() {
- // static methods only
- }
-
- public static WebService.NewParam createRootQualifierParameter(WebService.NewAction action, QualifierParameterContext context) {
- return action.createParam(PARAM_QUALIFIER)
- .setDescription("Project qualifier. Filter the results with the specified qualifier. Possible values are:" + buildRootQualifiersDescription(context))
- .setPossibleValues(getRootQualifiers(context.getResourceTypes()));
- }
-
- public static WebService.NewParam createRootQualifiersParameter(WebService.NewAction action, QualifierParameterContext context) {
- return action.createParam(PARAM_QUALIFIERS)
- .setDescription("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. " +
- "Possible values are:" + buildRootQualifiersDescription(context))
- .setPossibleValues(getRootQualifiers(context.getResourceTypes()));
- }
-
- public static WebService.NewParam createDefaultTemplateQualifierParameter(WebService.NewAction action, QualifierParameterContext context) {
- return action.createParam(PARAM_QUALIFIER)
- .setDescription("Project qualifier. Filter the results with the specified qualifier. Possible values are:" + buildDefaultTemplateQualifiersDescription(context))
- .setPossibleValues(getDefaultTemplateQualifiers(context.getResourceTypes()));
- }
-
- public static WebService.NewParam createQualifiersParameter(WebService.NewAction action, QualifierParameterContext context) {
- return action.createParam(PARAM_QUALIFIERS)
- .setDescription(
- "Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:" + buildAllQualifiersDescription(context))
- .setPossibleValues(getAllQualifiers(context.getResourceTypes()));
- }
-
- private static Set<String> getRootQualifiers(ResourceTypes resourceTypes) {
- return resourceTypes.getRoots().stream()
- .map(ResourceType::getQualifier)
- .collect(Collectors.toCollection(TreeSet::new));
- }
-
- private static Set<String> getDefaultTemplateQualifiers(ResourceTypes resourceTypes) {
- return resourceTypes.getRoots().stream()
- .map(ResourceType::getQualifier)
- .collect(Collectors.toCollection(TreeSet::new));
- }
-
- private static Set<String> getAllQualifiers(ResourceTypes resourceTypes) {
- return resourceTypes.getAll().stream()
- .map(ResourceType::getQualifier)
- .collect(Collectors.toCollection(TreeSet::new));
- }
-
- private static String buildDefaultTemplateQualifiersDescription(QualifierParameterContext context) {
- return buildQualifiersDescription(context, getDefaultTemplateQualifiers(context.getResourceTypes()));
- }
-
- private static String buildRootQualifiersDescription(QualifierParameterContext context) {
- return buildQualifiersDescription(context, getRootQualifiers(context.getResourceTypes()));
- }
-
- private static String buildAllQualifiersDescription(QualifierParameterContext context) {
- return buildQualifiersDescription(context, getAllQualifiers(context.getResourceTypes()));
- }
-
- private static String buildQualifiersDescription(QualifierParameterContext context, Set<String> qualifiers) {
- StringBuilder description = new StringBuilder();
- description.append("<ul>");
- String qualifierPattern = "<li>%s - %s</li>";
- for (String qualifier : qualifiers) {
- description.append(format(qualifierPattern, qualifier, qualifierLabel(context, qualifier)));
- }
- description.append("</ul>");
-
- return description.toString();
- }
-
- private static String qualifierLabel(QualifierParameterContext context, String qualifier) {
- String qualifiersPropertyPrefix = "qualifiers.";
- return context.getI18n().message(Locale.ENGLISH, qualifiersPropertyPrefix + qualifier, "no description available");
- }
-
- public static class QualifierParameterContext {
- private final I18n i18n;
- private final ResourceTypes resourceTypes;
-
- private QualifierParameterContext(I18n i18n, ResourceTypes resourceTypes) {
- this.i18n = i18n;
- this.resourceTypes = resourceTypes;
- }
-
- public static QualifierParameterContext newQualifierParameterContext(I18n i18n, ResourceTypes resourceTypes) {
- return new QualifierParameterContext(i18n, resourceTypes);
- }
-
- public I18n getI18n() {
- return i18n;
- }
-
- public ResourceTypes getResourceTypes() {
- return resourceTypes;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.utils.Paging;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonarqube.ws.Common;
-
-import static com.google.common.base.Strings.nullToEmpty;
-
-public class WsResponseCommonFormat {
-
- private final Languages languages;
-
- public WsResponseCommonFormat(Languages languages) {
- this.languages = languages;
- }
-
- public Common.Paging.Builder formatPaging(Paging paging) {
- return Common.Paging.newBuilder()
- .setPageIndex(paging.pageIndex())
- .setPageSize(paging.pageSize())
- .setTotal(paging.total());
- }
-
- public Common.Rule.Builder formatRule(RuleDefinitionDto rule) {
- Common.Rule.Builder builder = Common.Rule.newBuilder()
- .setKey(rule.getKey().toString())
- .setName(nullToEmpty(rule.getName()))
- .setStatus(Common.RuleStatus.valueOf(rule.getStatus().name()));
-
- builder.setLang(nullToEmpty(rule.getLanguage()));
- Language lang = languages.get(rule.getLanguage());
- if (lang != null) {
- builder.setLangName(lang.getName());
- }
- return builder;
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.Optional;
-import com.google.common.collect.ImmutableSet;
-import com.google.protobuf.Message;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.apache.commons.io.IOUtils;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.util.ProtobufJsonFormat;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
-
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.sonarqube.ws.MediaTypes.JSON;
-import static org.sonarqube.ws.MediaTypes.PROTOBUF;
-
-public class WsUtils {
-
- private static final Set<String> MODULE_OR_DIR_QUALIFIERS = ImmutableSet.of(Qualifiers.MODULE, Qualifiers.DIRECTORY);
-
- private WsUtils() {
- // only statics
- }
-
- public static void writeProtobuf(Message msg, Request request, Response response) {
- OutputStream output = response.stream().output();
- try {
- if (request.getMediaType().equals(PROTOBUF)) {
- response.stream().setMediaType(PROTOBUF);
- msg.writeTo(output);
- } else {
- response.stream().setMediaType(JSON);
- try (JsonWriter writer = JsonWriter.of(new OutputStreamWriter(output, UTF_8))) {
- ProtobufJsonFormat.write(msg, writer);
- }
- }
- } catch (Exception e) {
- throw new IllegalStateException("Error while writing protobuf message", e);
- } finally {
- IOUtils.closeQuietly(output);
- }
- }
-
- /**
- * @throws BadRequestException
- */
- public static void checkRequest(boolean expression, String message, Object... messageArguments) {
- if (!expression) {
- throw BadRequestException.create(format(message, messageArguments));
- }
- }
-
- public static void checkRequest(boolean expression, List<String> messages) {
- if (!expression) {
- throw BadRequestException.create(messages);
- }
- }
-
- /**
- * @throws NotFoundException if the value if null
- * @return the value
- */
- public static <T> T checkFound(@Nullable T value, String message, Object... messageArguments) {
- if (value == null) {
- throw new NotFoundException(format(message, messageArguments));
- }
-
- return value;
- }
-
- /**
- * @throws NotFoundException if the value is not present
- * @return the value
- */
- public static <T> T checkFoundWithOptional(Optional<T> value, String message, Object... messageArguments) {
- if (!value.isPresent()) {
- throw new NotFoundException(format(message, messageArguments));
- }
-
- return value.get();
- }
-
- public static <T> T checkFoundWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
- if (!value.isPresent()) {
- throw new NotFoundException(format(message, messageArguments));
- }
-
- return value.get();
- }
-
- public static <T> T checkStateWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
- if (!value.isPresent()) {
- throw new IllegalStateException(format(message, messageArguments));
- }
-
- return value.get();
- }
-
- public static void checkComponentNotAModuleAndNotADirectory(ComponentDto component) {
- checkRequest(!MODULE_OR_DIR_QUALIFIERS.contains(component.qualifier()), "Operation not supported for module or directory components");
- }
-}
package org.sonar.server.ws;
import javax.annotation.ParametersAreNonnullByDefault;
+
+++ /dev/null
-{
- "errors": [
- {
- "msg": "The web service '/api/...' doesn't exists anymore, please read its documentation to use alternatives"
- }
- ]
-}
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new IssueWsModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 31);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 30);
}
}
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.view.index.ViewIndexer;
import org.sonar.server.ws.WsActionTester;
-import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Issues.Component;
import org.sonarqube.ws.Issues.Issue;
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private Languages languages = new Languages();
- private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl());
+ private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new AvatarResolverImpl());
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer);
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat,
import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
-import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Common.FacetValue;
import org.sonarqube.ws.Issues.SearchWsResponse;
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(db.getDbClient(), Clock.systemUTC(), userSession);
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, db.getDbClient(), new TransitionService(userSession, null));
private Languages languages = new Languages();
- private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl());
+ private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new AvatarResolverImpl());
private WsActionTester ws = new WsActionTester(
new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat,
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
-import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Common.Severity;
import org.sonarqube.ws.Issues;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PAGE_INDEX;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PAGE_SIZE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
public class SearchActionTest {
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private Languages languages = new Languages();
- private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl());
+ private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new AvatarResolverImpl());
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat,
new MapSettings().asConfig(), System2.INSTANCE, dbClient));
private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);
import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
-import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonar.test.JsonAssert;
import static org.assertj.core.api.Assertions.assertThat;
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private Languages languages = new Languages();
- private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl());
+ private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new AvatarResolverImpl());
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer);
private SearchAction underTest = new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat,
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class CacheWriterTest {
- private Writer writer = new StringWriter();
- private CacheWriter underTest = new CacheWriter(writer);
-
- @Test
- public void write_content_when_closing_resource() throws IOException {
- underTest.write("content");
- assertThat(writer.toString()).isEmpty();
-
- underTest.close();
-
- assertThat(writer.toString()).isEqualTo("content");
- }
-
- @Test
- public void close_encapsulated_writer_once() throws IOException {
- writer = mock(Writer.class);
- underTest = new CacheWriter(writer);
-
- underTest.close();
- underTest.close();
-
- verify(writer, times(1)).close();
- }
-}
package org.sonar.server.ws;
import java.io.ByteArrayInputStream;
-import java.io.IOException;
import java.util.Arrays;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
}
@Test
- public void redirect_delete_api_properties_to_api_settings_reset() throws Exception {
+ public void redirect_delete_api_properties_to_api_settings_reset() {
when(request.getRequestURI()).thenReturn("/api/properties/my.property");
when(request.getParameter("resource")).thenReturn("my_project");
when(request.getMethod()).thenReturn("DELETE");
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.Response;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.api.utils.text.XmlWriter;
-
-public class DumbResponse implements Response {
- private InMemoryStream stream;
-
- private final ByteArrayOutputStream output = new ByteArrayOutputStream();
-
- private Map<String, String> headers = Maps.newHashMap();
-
- 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;
- }
-
- public String outputAsString() {
- return new String(output.toByteArray(), StandardCharsets.UTF_8);
- }
- }
-
- @Override
- public JsonWriter newJsonWriter() {
- return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
- }
-
- @Override
- public XmlWriter newXmlWriter() {
- return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
- }
-
- @Override
- public InMemoryStream stream() {
- if (stream == null) {
- stream = new InMemoryStream();
- }
- 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;
- }
-
- public Collection<String> getHeaderNames() {
- return headers.keySet();
- }
-
- @CheckForNull
- public String getHeader(String name){
- return headers.get(name);
- }
-
- public byte[] getFlushedOutput() {
- try {
- output.flush();
- return output.toByteArray();
- } catch (IOException e) {
- throw Throwables.propagate(e);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.server.ws.Request;
-import org.sonar.server.exceptions.ServerException;
-
-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 RemovedWebServiceHandlerTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void throw_server_exception() throws Exception {
- Request request = mock(Request.class);
- when(request.getPath()).thenReturn("/api/resources/index");
-
- try {
- RemovedWebServiceHandler.INSTANCE.handle(request, null);
- fail();
- } catch (ServerException e) {
- assertThat(e.getMessage()).isEqualTo("The web service '/api/resources/index' doesn't exist anymore, please read its documentation to use alternatives");
- assertThat(e.httpCode()).isEqualTo(410);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.collect.ImmutableMap;
-import com.google.common.net.HttpHeaders;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Part;
-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.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class ServletRequestTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private HttpServletRequest source = mock(HttpServletRequest.class);
-
- private ServletRequest underTest = new ServletRequest(source);
-
- @Test
- public void call_method() {
- underTest.method();
-
- verify(source).getMethod();
- }
-
- @Test
- public void getMediaType() {
- when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
- when(source.getRequestURI()).thenReturn("/path/to/resource/search");
-
- assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.JSON);
- }
-
- @Test
- public void default_media_type_is_octet_stream() {
- when(source.getRequestURI()).thenReturn("/path/to/resource/search");
-
- assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.DEFAULT);
- }
-
- @Test
- public void media_type_taken_in_url_first() {
- when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
- when(source.getRequestURI()).thenReturn("/path/to/resource/search.protobuf");
-
- assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.PROTOBUF);
- }
-
- @Test
- public void has_param_from_source() {
- when(source.getParameterMap()).thenReturn(ImmutableMap.of("param", new String[] {"value"}));
- ServletRequest request = new ServletRequest(source);
- assertThat(request.hasParam("param")).isTrue();
- }
-
- @Test
- public void read_param_from_source() {
- when(source.getParameter("param")).thenReturn("value");
-
- assertThat(underTest.readParam("param")).isEqualTo("value");
- }
-
- @Test
- public void read_multi_param_from_source_with_values() {
- when(source.getParameterValues("param")).thenReturn(new String[]{"firstValue", "secondValue", "thirdValue"});
-
- List<String> result = underTest.readMultiParam("param");
-
- assertThat(result).containsExactly("firstValue", "secondValue", "thirdValue");
- }
-
- @Test
- public void read_multi_param_from_source_with_one_value() {
- when(source.getParameterValues("param")).thenReturn(new String[]{"firstValue"});
-
- List<String> result = underTest.readMultiParam("param");
-
- assertThat(result).containsExactly("firstValue");
- }
-
- @Test
- public void read_multi_param_from_source_without_value() {
- when(source.getParameterValues("param")).thenReturn(null);
-
- List<String> result = underTest.readMultiParam("param");
-
- assertThat(result).isEmpty();
- }
-
- @Test
- public void read_input_stream() throws Exception {
- when(source.getContentType()).thenReturn("multipart/form-data");
- InputStream file = mock(InputStream.class);
- Part part = mock(Part.class);
- when(part.getInputStream()).thenReturn(file);
- when(part.getSize()).thenReturn(10L);
- when(source.getPart("param1")).thenReturn(part);
-
- assertThat(underTest.readInputStreamParam("param1")).isEqualTo(file);
- assertThat(underTest.readInputStreamParam("param2")).isNull();
- }
-
- @Test
- public void read_no_input_stream_when_part_size_is_zero() throws Exception {
- when(source.getContentType()).thenReturn("multipart/form-data");
- InputStream file = mock(InputStream.class);
- Part part = mock(Part.class);
- when(part.getInputStream()).thenReturn(file);
- when(part.getSize()).thenReturn(0L);
- when(source.getPart("param1")).thenReturn(part);
-
- assertThat(underTest.readInputStreamParam("param1")).isNull();
- }
-
- @Test
- public void return_no_input_stream_when_content_type_is_not_multipart() {
- when(source.getContentType()).thenReturn("multipart/form-data");
-
- assertThat(underTest.readInputStreamParam("param1")).isNull();
- }
-
- @Test
- public void return_no_input_stream_when_content_type_is_null() {
- when(source.getContentType()).thenReturn(null);
-
- assertThat(underTest.readInputStreamParam("param1")).isNull();
- }
-
- @Test
- public void returns_null_when_invalid_part() throws Exception {
- when(source.getContentType()).thenReturn("multipart/form-data");
- InputStream file = mock(InputStream.class);
- Part part = mock(Part.class);
- when(part.getSize()).thenReturn(0L);
- when(part.getInputStream()).thenReturn(file);
- doThrow(IllegalArgumentException.class).when(source).getPart("param1");
-
- assertThat(underTest.readInputStreamParam("param1")).isNull();
- }
-
- @Test
- public void getPath() {
- when(source.getRequestURI()).thenReturn("/sonar/path/to/resource/search");
- when(source.getContextPath()).thenReturn("/sonar");
-
- assertThat(underTest.getPath()).isEqualTo("/path/to/resource/search");
- }
-
- @Test
- public void to_string() {
- when(source.getRequestURL()).thenReturn(new StringBuffer("http:localhost:9000/api/issues"));
- assertThat(underTest.toString()).isEqualTo("http:localhost:9000/api/issues");
-
- when(source.getQueryString()).thenReturn("components=sonar");
-
- assertThat(underTest.toString()).isEqualTo("http:localhost:9000/api/issues?components=sonar");
- }
-
- @Test
- public void header_returns_the_value_of_http_header() {
- when(source.getHeader("Accept")).thenReturn("text/plain");
- assertThat(underTest.header("Accept")).hasValue("text/plain");
- }
-
- @Test
- public void header_is_empty_if_absent_from_request() {
- when(source.getHeader("Accept")).thenReturn(null);
- assertThat(underTest.header("Accept")).isEmpty();
- }
-
- @Test
- public void header_has_empty_value_if_present_in_request_without_value() {
- when(source.getHeader("Accept")).thenReturn("");
- assertThat(underTest.header("Accept")).hasValue("");
- }
-
- @Test
- public void getReader() throws IOException {
- BufferedReader reader = new BufferedReader(new StringReader("foo"));
- when(source.getReader()).thenReturn(reader);
-
- assertThat(underTest.getReader()).isEqualTo(reader);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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 javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonarqube.ws.MediaTypes.JSON;
-import static org.sonarqube.ws.MediaTypes.XML;
-
-public class ServletResponseTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private ServletOutputStream output = mock(ServletOutputStream.class);
- private HttpServletResponse response = mock(HttpServletResponse.class);
-
- private ServletResponse underTest = new ServletResponse(response);
-
- @Before
- public void setUp() throws Exception {
- when(response.getOutputStream()).thenReturn(output);
- }
-
- @Test
- public void test_default_header() {
- verify(response).setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
- }
-
- @Test
- public void set_header() {
- underTest.setHeader("header", "value");
-
- verify(response).setHeader("header", "value");
- }
-
- @Test
- public void get_header() {
- underTest.getHeader("header");
-
- verify(response).getHeader("header");
- }
-
- @Test
- public void get_header_names() {
- underTest.getHeaderNames();
-
- verify(response).getHeaderNames();
- }
-
- @Test
- public void test_default_status() {
- verify(response).setStatus(200);
- }
-
- @Test
- public void set_status() {
- underTest.stream().setStatus(404);
-
- verify(response).setStatus(404);
- }
-
- @Test
- public void test_output() {
- assertThat(underTest.stream().output()).isEqualTo(output);
- }
-
-
- @Test
- public void test_reset() {
- underTest.stream().reset();
-
- verify(response).reset();
- }
-
- @Test
- public void test_newJsonWriter() throws Exception {
- underTest.newJsonWriter();
-
- verify(response).setContentType(JSON);
- verify(response).getOutputStream();
- }
-
- @Test
- public void test_newXmlWriter() throws Exception {
- underTest.newXmlWriter();
-
- verify(response).setContentType(XML);
- verify(response).getOutputStream();
- }
-
- @Test
- public void test_noContent() throws Exception {
- underTest.noContent();
-
- verify(response).setStatus(204);
- verify(response, never()).getOutputStream();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.ArrayListMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Maps;
-import com.google.protobuf.GeneratedMessageV3;
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import org.apache.commons.io.IOUtils;
-import org.sonar.api.impl.ws.PartImpl;
-import org.sonar.api.impl.ws.ValidatingRequest;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
-import static org.sonarqube.ws.MediaTypes.PROTOBUF;
-
-public class TestRequest extends ValidatingRequest {
-
- private final ListMultimap<String, String> multiParams = ArrayListMultimap.create();
- private final Map<String, String> params = new HashMap<>();
- private final Map<String, String> headers = new HashMap<>();
- private final Map<String, Part> parts = Maps.newHashMap();
- private String payload = "";
- private boolean payloadConsumed = false;
- private String method = "GET";
- private String mimeType = "application/octet-stream";
- private String path;
-
- @Override
- public BufferedReader getReader() {
- checkState(!payloadConsumed, "Payload already consumed");
- if (payload == null) {
- return super.getReader();
- }
-
- BufferedReader res = new BufferedReader(new StringReader(payload));
- payloadConsumed = true;
- return res;
- }
-
- public TestRequest setPayload(String payload) {
- checkState(!payloadConsumed, "Payload already consumed");
-
- this.payload = payload;
- return this;
- }
-
- @Override
- protected String readParam(String key) {
- return params.get(key);
- }
-
- @Override
- protected List<String> readMultiParam(String key) {
- return multiParams.get(key);
- }
-
- @Override
- protected InputStream readInputStreamParam(String key) {
- String value = readParam(key);
- if (value == null) {
- return null;
- }
- return IOUtils.toInputStream(value);
- }
-
- @Override
- protected Part readPart(String key) {
- return parts.get(key);
- }
-
- public TestRequest setPart(String key, InputStream input, String fileName) {
- parts.put(key, new PartImpl(input, fileName));
- return this;
- }
-
- @Override
- public String method() {
- return method;
- }
-
- @Override
- public boolean hasParam(String key) {
- return params.containsKey(key) || multiParams.containsKey(key);
- }
-
- @Override
- public String getPath() {
- return path;
- }
-
- @Override
- public Map<String, String[]> getParams() {
- ArrayListMultimap<String, String> result = ArrayListMultimap.create(multiParams);
- params.forEach(result::put);
- return result.asMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toArray(new String[0])));
- }
-
- public TestRequest setPath(String path) {
- this.path = path;
- return this;
- }
-
- public TestRequest setMethod(String method) {
- checkNotNull(method);
- this.method = method;
- return this;
- }
-
- @Override
- public String getMediaType() {
- return mimeType;
- }
-
- public TestRequest setMediaType(String type) {
- checkNotNull(type);
- this.mimeType = type;
- return this;
- }
-
- public TestRequest setParam(String key, String value) {
- checkNotNull(key);
- checkNotNull(value);
- this.params.put(key, value);
- return this;
- }
-
- public TestRequest setMultiParam(String key, List<String> values) {
- requireNonNull(key);
- requireNonNull(values);
-
- multiParams.putAll(key, values);
-
- return this;
- }
-
- @Override
- public Map<String, String> getHeaders() {
- return ImmutableMap.copyOf(headers);
- }
-
- @Override
- public Optional<String> header(String name) {
- return Optional.ofNullable(headers.get(name));
- }
-
- public TestRequest setHeader(String name, String value) {
- headers.put(requireNonNull(name), requireNonNull(value));
- return this;
- }
-
- public TestResponse execute() {
- try {
- DumbResponse response = new DumbResponse();
- action().handler().handle(this, response);
- return new TestResponse(response);
- } catch (Exception e) {
- throw Throwables.propagate(e);
- }
- }
-
- public <T extends GeneratedMessageV3> T executeProtobuf(Class<T> protobufClass) {
- return setMediaType(PROTOBUF).execute().getInputObject(protobufClass);
- }
-
- @Override
- public String toString() {
- return path;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.protobuf.GeneratedMessageV3;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import javax.annotation.CheckForNull;
-import org.sonar.test.JsonAssert;
-
-public class TestResponse {
-
- private final DumbResponse dumbResponse;
-
- TestResponse(DumbResponse dumbResponse) {
- this.dumbResponse = dumbResponse;
- }
-
- public InputStream getInputStream() {
- return new ByteArrayInputStream(dumbResponse.getFlushedOutput());
- }
-
- public <T extends GeneratedMessageV3> T getInputObject(Class<T> protobufClass) {
- try (InputStream input = getInputStream()) {
- Method parseFromMethod = protobufClass.getMethod("parseFrom", InputStream.class);
- @SuppressWarnings("unchecked")
- T result = (T) parseFromMethod.invoke(null, input);
- return result;
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- }
-
- public String getInput() {
- return new String(dumbResponse.getFlushedOutput(), StandardCharsets.UTF_8);
- }
-
- public String getMediaType() {
- return dumbResponse.stream().mediaType();
- }
-
- public int getStatus() {
- return dumbResponse.stream().status();
- }
-
- @CheckForNull
- public String getHeader(String headerKey) {
- return dumbResponse.getHeader(headerKey);
- }
-
- public void assertJson(String expectedJson) {
- JsonAssert.assertJson(getInput()).isSimilarTo(expectedJson);
- }
-
- /**
- * Compares JSON response with JSON file available in classpath. For example if class
- * is org.foo.BarTest and filename is index.json, then file must be located
- * at src/test/resources/org/foo/BarTest/index.json.
- *
- * @param clazz the test class
- * @param expectedJsonFilename name of the file containing the expected JSON
- */
- public void assertJson(Class clazz, String expectedJsonFilename) {
- String path = clazz.getSimpleName() + "/" + expectedJsonFilename;
- URL url = clazz.getResource(path);
- if (url == null) {
- throw new IllegalStateException("Cannot find " + path);
- }
- JsonAssert.assertJson(getInput()).isSimilarTo(url);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.util.function.Consumer;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.catalina.connector.ClientAbortException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.mockito.Mockito;
-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.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonarqube.ws.MediaTypes;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-import static org.apache.commons.lang.StringUtils.substringBeforeLast;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class WebServiceEngineTest {
-
- @Rule
- public LogTester logTester = new LogTester();
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void load_ws_definitions_at_startup() {
- WebServiceEngine underTest = new WebServiceEngine(new WebService[] {
- newWs("api/foo/index", a -> {
- }),
- newWs("api/bar/index", a -> {
- })
- });
- underTest.start();
- try {
- assertThat(underTest.controllers())
- .extracting(WebService.Controller::path)
- .containsExactlyInAnyOrder("api/foo", "api/bar");
- } finally {
- underTest.stop();
- }
- }
-
- @Test
- public void ws_returns_successful_response() {
- Request request = new TestRequest().setPath("/api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void accept_path_that_does_not_start_with_slash() {
- Request request = new TestRequest().setPath("api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void request_path_can_contain_valid_media_type() {
- Request request = new TestRequest().setPath("api/ping.json");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void bad_request_if_action_suffix_is_not_supported() {
- Request request = new TestRequest().setPath("/api/ping.bat");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown action extension: bat\"}]}");
- }
-
- @Test
- public void test_response_with_no_content() {
- Request request = new TestRequest().setPath("api/foo");
-
- RequestHandler handler = (req, resp) -> resp.noContent();
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler(handler)));
-
- assertThat(response.stream().outputAsString()).isEmpty();
- assertThat(response.stream().status()).isEqualTo(204);
- }
-
- @Test
- public void return_404_if_controller_does_not_exist() {
- Request request = new TestRequest().setPath("xxx/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
- }
-
- @Test
- public void return_404_if_action_does_not_exist() {
- Request request = new TestRequest().setPath("api/xxx");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}");
- assertThat(response.stream().status()).isEqualTo(404);
- }
-
- @Test
- public void fail_if_method_GET_is_not_allowed() {
- Request request = new TestRequest().setMethod("GET").setPath("api/foo");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setPost(true)));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method POST is required\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
- }
-
- @Test
- public void POST_is_considered_as_GET_if_POST_is_not_supported() {
- Request request = new TestRequest().setMethod("POST").setPath("api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void method_PUT_is_not_allowed() {
- Request request = new TestRequest().setMethod("PUT").setPath("/api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method PUT is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
- }
-
- @Test
- public void method_DELETE_is_not_allowed() {
- Request request = new TestRequest().setMethod("DELETE").setPath("api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> {
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method DELETE is not allowed\"}]}");
- assertThat(response.stream().status()).isEqualTo(405);
- }
-
- @Test
- public void method_POST_is_required() {
- Request request = new TestRequest().setMethod("POST").setPath("api/ping");
-
- DumbResponse response = run(request, newPingWs(a -> a.setPost(true)));
-
- assertThat(response.stream().outputAsString()).isEqualTo("pong");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void fail_if_reading_an_undefined_parameter() {
- Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> request.param("unknown"))));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"BUG - parameter 'unknown' is undefined for action 'foo'\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- }
-
- @Test
- public void fail_if_request_does_not_have_required_parameter() {
- Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
-
- DumbResponse response = run(request, newWs("api/foo", a -> {
- a.createParam("bar").setRequired(true);
- a.setHandler((req, resp) -> request.mandatoryParam("bar"));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The 'bar' parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- }
-
- @Test
- public void fail_if_request_does_not_have_required_parameter_even_if_handler_does_not_require_it() {
- Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
-
- DumbResponse response = run(request, newWs("api/foo", a -> {
- a.createParam("bar").setRequired(true);
- // do not use mandatoryParam("bar")
- a.setHandler((req, resp) -> request.param("bar"));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The 'bar' parameter is missing\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- }
-
- @Test
- public void use_default_value_of_optional_parameter() {
- Request request = new TestRequest().setPath("api/print");
-
- DumbResponse response = run(request, newWs("api/print", a -> {
- a.createParam("message").setDefaultValue("hello");
- a.setHandler((req, resp) -> resp.stream().output().write(req.param("message").getBytes(UTF_8)));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("hello");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void use_request_parameter_on_parameter_with_default_value() {
- Request request = new TestRequest().setPath("api/print").setParam("message", "bar");
-
- DumbResponse response = run(request, newWs("api/print", a -> {
- a.createParam("message").setDefaultValue("default_value");
- a.setHandler((req, resp) -> resp.stream().output().write(req.param("message").getBytes(UTF_8)));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("bar");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void accept_parameter_value_within_defined_possible_values() {
- Request request = new TestRequest().setPath("api/foo").setParam("format", "json");
-
- DumbResponse response = run(request, newWs("api/foo", a -> {
- a.createParam("format").setPossibleValues("json", "xml");
- a.setHandler((req, resp) -> resp.stream().output().write(req.mandatoryParam("format").getBytes(UTF_8)));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("json");
- assertThat(response.stream().status()).isEqualTo(200);
- }
-
- @Test
- public void fail_if_parameter_value_is_not_in_defined_possible_values() {
- Request request = new TestRequest().setPath("api/foo").setParam("format", "yml");
-
- DumbResponse response = run(request, newWs("api/foo", a -> {
- a.createParam("format").setPossibleValues("json", "xml");
- a.setHandler((req, resp) -> resp.stream().output().write(req.mandatoryParam("format").getBytes(UTF_8)));
- }));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Value of parameter 'format' (yml) must be one of: [json, xml]\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- }
-
- @Test
- public void return_500_on_internal_error() {
- Request request = new TestRequest().setPath("api/foo");
-
- DumbResponse response = run(request, newFailWs());
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"An error has occurred. Please contact your administrator\"}]}");
- assertThat(response.stream().status()).isEqualTo(500);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- assertThat(logTester.logs(LoggerLevel.ERROR)).filteredOn(l -> l.contains("Fail to process request api/foo")).isNotEmpty();
- }
-
- @Test
- public void return_400_on_BadRequestException_with_single_message() {
- Request request = new TestRequest().setPath("api/foo");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
- throw BadRequestException.create("Bad request !");
- })));
-
- assertThat(response.stream().outputAsString()).isEqualTo(
- "{\"errors\":[{\"msg\":\"Bad request !\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
- }
-
- @Test
- public void return_400_on_BadRequestException_with_multiple_messages() {
- Request request = new TestRequest().setPath("api/foo");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
- throw BadRequestException.create("one", "two", "three");
- })));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":["
- + "{\"msg\":\"one\"},"
- + "{\"msg\":\"two\"},"
- + "{\"msg\":\"three\"}"
- + "]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
- }
-
- @Test
- public void return_error_message_containing_character_percent() {
- Request request = new TestRequest().setPath("api/foo");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
- throw new IllegalArgumentException("this should not fail %s");
- })));
-
- assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"this should not fail %s\"}]}");
- assertThat(response.stream().status()).isEqualTo(400);
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- }
-
- @Test
- public void send_response_headers() {
- Request request = new TestRequest().setPath("api/foo");
-
- DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> resp.setHeader("Content-Disposition", "attachment; filename=foo.zip"))));
-
- assertThat(response.getHeader("Content-Disposition")).isEqualTo("attachment; filename=foo.zip");
- }
-
- @Test
- public void support_aborted_request_when_response_is_already_committed() {
- Request request = new TestRequest().setPath("api/foo");
- Response response = mockServletResponse(true);
-
- run(request, response, newClientAbortWs());
-
- // response is committed (status is already sent), so status can't be changed
- verify(response.stream(), never()).setStatus(anyInt());
-
- assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Request api/foo has been aborted by client");
- }
-
- @Test
- public void support_aborted_request_when_response_is_not_committed() {
- Request request = new TestRequest().setPath("api/foo");
- Response response = mockServletResponse(false);
-
- run(request, response, newClientAbortWs());
-
- verify(response.stream()).setStatus(299);
- assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Request api/foo has been aborted by client");
- }
-
- @Test
- public void internal_error_when_response_is_already_committed() {
- Request request = new TestRequest().setPath("api/foo");
- Response response = mockServletResponse(true);
-
- run(request, response, newFailWs());
-
- // response is committed (status is already sent), so status can't be changed
- verify(response.stream(), never()).setStatus(anyInt());
- assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request api/foo");
- }
-
- @Test
- public void internal_error_when_response_is_not_committed() {
- Request request = new TestRequest().setPath("api/foo");
- Response response = mockServletResponse(false);
-
- run(request, response, newFailWs());
-
- verify(response.stream()).setStatus(500);
- assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request api/foo");
- }
-
- @Test
- public void fail_when_start_in_not_called() {
- Request request = new TestRequest().setPath("/api/ping");
- DumbResponse response = new DumbResponse();
- WebServiceEngine underTest = new WebServiceEngine(new WebService[] {newPingWs(a -> {
- })});
-
- underTest.execute(request, response);
-
- assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request /api/ping");
- }
-
- private static WebService newWs(String path, Consumer<WebService.NewAction> consumer) {
- return context -> {
- WebService.NewController controller = context.createController(substringBeforeLast(path, "/"));
- WebService.NewAction action = createNewDefaultAction(controller, substringAfterLast(path, "/"));
- action.setHandler((request, response) -> {
- });
- consumer.accept(action);
- controller.done();
- };
- }
-
- private static WebService newPingWs(Consumer<WebService.NewAction> consumer) {
- return newWs("api/ping", a -> {
- a.setHandler((request, response) -> response.stream().output().write("pong".getBytes(UTF_8)));
- consumer.accept(a);
- });
- }
-
- private static WebService newFailWs() {
- return newWs("api/foo", a -> a.setHandler((req, resp) -> {
- throw new IllegalStateException("BOOM");
- }));
- }
-
- private static DumbResponse run(Request request, WebService... webServices) {
- DumbResponse response = new DumbResponse();
- return (DumbResponse) run(request, response, webServices);
- }
-
- private static Response run(Request request, Response response, WebService... webServices) {
- WebServiceEngine underTest = new WebServiceEngine(webServices);
- underTest.start();
- try {
- underTest.execute(request, response);
- return response;
- } finally {
- underTest.stop();
- }
- }
-
- private static Response mockServletResponse(boolean committed) {
- Response response = mock(Response.class, Mockito.RETURNS_DEEP_STUBS);
- ServletResponse.ServletStream servletStream = mock(ServletResponse.ServletStream.class, Mockito.RETURNS_DEEP_STUBS);
- when(response.stream()).thenReturn(servletStream);
- HttpServletResponse httpServletResponse = mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS);
- when(httpServletResponse.isCommitted()).thenReturn(committed);
- when(servletStream.response()).thenReturn(httpServletResponse);
- return response;
- }
-
- private static WebService newClientAbortWs() {
- return newWs("api/foo", a -> a.setHandler((req, resp) -> {
- throw new ClientAbortException();
- }));
- }
-
- private static WebService.NewAction createNewDefaultAction(WebService.NewController controller, String key) {
- return controller
- .createAction(key)
- .setDescription("Dummy Description")
- .setSince("5.3")
- .setResponseExample(WebServiceEngineTest.class.getResource("web-service-engine-test.txt"));
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.collect.Iterables;
-import org.sonar.api.server.ws.WebService;
-
-public class WsActionTester {
-
- public static final String CONTROLLER_KEY = "test";
- private final WebService.Action action;
-
- public WsActionTester(WsAction wsAction) {
- WebService.Context context = new WebService.Context();
- WebService.NewController newController = context.createController(CONTROLLER_KEY);
- wsAction.define(newController);
- newController.done();
- action = Iterables.get(context.controller(CONTROLLER_KEY).actions(), 0);
- }
-
- public WebService.Action getDef() {
- return action;
- }
-
- public TestRequest newRequest() {
- TestRequest request = new TestRequest();
- request.setAction(action);
- return request;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.collect.Maps;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.io.IOUtils;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.api.impl.ws.PartImpl;
-import org.sonar.api.impl.ws.ValidatingRequest;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.api.utils.text.XmlWriter;
-import org.sonar.server.ws.WsTester.TestResponse.TestStream;
-import org.sonar.test.JsonAssert;
-import org.sonarqube.ws.MediaTypes;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.Objects.requireNonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.ws.RequestVerifier.verifyRequest;
-
-/**
- * @since 4.2
- * @deprecated use {@link WsActionTester} in preference to this class
- */
-@Deprecated
-public class WsTester {
-
- public static class TestRequest extends ValidatingRequest {
-
- private final String method;
- private String path;
- private String mediaType = MediaTypes.JSON;
-
- private Map<String, String> params = Maps.newHashMap();
- private Map<String, String> headers = Maps.newHashMap();
- private final Map<String, Part> parts = Maps.newHashMap();
-
- private TestRequest(String method) {
- this.method = method;
- }
-
- @Override
- public String method() {
- return method;
- }
-
- @Override
- public String getMediaType() {
- return mediaType;
- }
-
- public TestRequest setMediaType(String s) {
- this.mediaType = s;
- return this;
- }
-
- @Override
- public boolean hasParam(String key) {
- return params.keySet().contains(key);
- }
-
- @Override
- public Optional<String> header(String name) {
- return Optional.ofNullable(headers.get(name));
- }
-
- public TestRequest setHeader(String name, String value) {
- this.headers.put(requireNonNull(name), requireNonNull(value));
- return this;
- }
-
- @Override
- public String getPath() {
- return path;
- }
-
- public TestRequest setPath(String path) {
- this.path = path;
- return this;
- }
-
- public TestRequest setParams(Map<String, String> m) {
- this.params = m;
- return this;
- }
-
- public TestRequest setParam(String key, @Nullable String value) {
- if (value != null) {
- params.put(key, value);
- }
- return this;
- }
-
- @Override
- protected String readParam(String key) {
- return params.get(key);
- }
-
- @Override
- protected List<String> readMultiParam(String key) {
- String value = params.get(key);
- return value == null ? emptyList() : singletonList(value);
- }
-
- @Override
- public Map<String, String[]> getParams() {
- return params.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new String[] {e.getValue()}));
- }
-
- @Override
- protected InputStream readInputStreamParam(String key) {
- String param = readParam(key);
-
- return param == null ? null : IOUtils.toInputStream(param);
- }
-
- @Override
- protected Part readPart(String key) {
- return parts.get(key);
- }
-
- public TestRequest setPart(String key, InputStream input, String fileName) {
- parts.put(key, new PartImpl(input, fileName));
- return this;
- }
-
- public Result execute() throws Exception {
- TestResponse response = new TestResponse();
- verifyRequest(action(), this);
- action().handler().handle(this, response);
- return new Result(response);
- }
- }
-
- public static class TestResponse implements Response {
-
- private TestStream stream;
-
- private Map<String, String> headers = Maps.newHashMap();
-
- public class TestStream implements Response.Stream {
- private String mediaType;
- private int status;
-
- @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;
- }
- }
-
- private final ByteArrayOutputStream output = new ByteArrayOutputStream();
-
- @Override
- public JsonWriter newJsonWriter() {
- return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
- }
-
- @Override
- public XmlWriter newXmlWriter() {
- return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
- }
-
- @Override
- public Stream stream() {
- if (stream == null) {
- stream = new TestStream();
- }
- 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 static class Result {
- private final TestResponse response;
-
- private Result(TestResponse response) {
- this.response = response;
- }
-
- public Result assertNoContent() {
- return assertStatus(HttpURLConnection.HTTP_NO_CONTENT);
- }
-
- public String outputAsString() {
- return new String(response.output.toByteArray(), StandardCharsets.UTF_8);
- }
-
- public byte[] output() {
- return response.output.toByteArray();
- }
-
- public Result assertJson(String expectedJson) {
- String json = outputAsString();
- JsonAssert.assertJson(json).isSimilarTo(expectedJson);
- return this;
- }
-
- /**
- * Compares JSON response with JSON file available in classpath. For example if class
- * is org.foo.BarTest and filename is index.json, then file must be located
- * at src/test/resources/org/foo/BarTest/index.json.
- *
- * @param clazz the test class
- * @param expectedJsonFilename name of the file containing the expected JSON
- */
- public Result assertJson(Class clazz, String expectedJsonFilename) {
- String path = clazz.getSimpleName() + "/" + expectedJsonFilename;
- URL url = clazz.getResource(path);
- if (url == null) {
- throw new IllegalStateException("Cannot find " + path);
- }
- String json = outputAsString();
- JsonAssert.assertJson(json).isSimilarTo(url);
- return this;
- }
-
- public Result assertNotModified() {
- return assertStatus(HttpURLConnection.HTTP_NOT_MODIFIED);
- }
-
- public Result assertStatus(int httpStatus) {
- assertThat(((TestStream) response.stream()).status()).isEqualTo(httpStatus);
- return this;
- }
-
- public Result assertHeader(String name, String value) {
- assertThat(response.getHeader(name)).isEqualTo(value);
- return this;
- }
- }
-
- private final WebService.Context context = new WebService.Context();
-
- public WsTester(WebService... webServices) {
- for (WebService webService : webServices) {
- webService.define(context);
- }
- }
-
- public WebService.Context context() {
- return context;
- }
-
- @CheckForNull
- public WebService.Controller controller(String key) {
- return context.controller(key);
- }
-
- @CheckForNull
- public WebService.Action action(String controllerKey, String actionKey) {
- WebService.Controller controller = context.controller(controllerKey);
- if (controller != null) {
- return controller.action(actionKey);
- }
- return null;
- }
-
- public TestRequest newGetRequest(String controllerKey, String actionKey) {
- return newRequest(controllerKey, actionKey, "GET");
- }
-
- public TestRequest newPostRequest(String controllerKey, String actionKey) {
- return newRequest(controllerKey, actionKey, "POST");
- }
-
- private TestRequest newRequest(String controllerKey, String actionKey, String method) {
- TestRequest request = new TestRequest(method);
- WebService.Controller controller = context.controller(controllerKey);
- if (controller == null) {
- throw new IllegalArgumentException(
- String.format("Controller '%s' is unknown, did you forget to call NewController.done()?", controllerKey));
- }
- WebService.Action action = controller.action(actionKey);
- if (action == null) {
- throw new IllegalArgumentException(
- String.format("Action '%s' not found on controller '%s'.", actionKey, controllerKey));
- }
- request.setAction(action);
- return request;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info 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.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonarqube.ws.Issues;
-import org.sonarqube.ws.MediaTypes;
-import org.sonarqube.ws.Permissions;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.test.ExceptionCauseMatcher.hasType;
-
-public class WsUtilsTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Rule
- public LogTester logger = new LogTester();
-
- @Test
- public void write_json_by_default() {
- TestRequest request = new TestRequest();
- DumbResponse response = new DumbResponse();
-
- Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
- WsUtils.writeProtobuf(msg, request, response);
-
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
- assertThat(response.outputAsString())
- .startsWith("{")
- .contains("\"key\":\"I1\"")
- .endsWith("}");
- }
-
- @Test
- public void write_protobuf() throws Exception {
- TestRequest request = new TestRequest();
- request.setMediaType(MediaTypes.PROTOBUF);
- DumbResponse response = new DumbResponse();
-
- Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
- WsUtils.writeProtobuf(msg, request, response);
-
- assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.PROTOBUF);
- assertThat(Issues.Issue.parseFrom(response.getFlushedOutput()).getKey()).isEqualTo("I1");
- }
-
- @Test
- public void rethrow_error_as_ISE_when_error_writing_message() {
- TestRequest request = new TestRequest();
- request.setMediaType(MediaTypes.PROTOBUF);
-
- Permissions.Permission message = Permissions.Permission.newBuilder().setName("permission-name").build();
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectCause(hasType(NullPointerException.class));
- expectedException.expectMessage("Error while writing protobuf message");
- // provoke NullPointerException
- WsUtils.writeProtobuf(message, null, new DumbResponse());
- }
-
- @Test
- public void checkRequest_ok() {
- WsUtils.checkRequest(true, "Missing param: %s", "foo");
- // do not fail
- }
-
- @Test
- public void checkRequest_ko() {
- expectedException.expect(BadRequestException.class);
- expectedException.expectMessage("Missing param: foo");
-
- WsUtils.checkRequest(false, "Missing param: %s", "foo");
- }
-
-}
--- /dev/null
+description = 'WebServer "API" to write Web Services'
+
+sonarqube {
+ properties {
+ property 'sonar.projectName', "${projectTitle} :: WebServer :: WS"
+ }
+}
+
+configurations {
+ tests
+
+ testCompile.extendsFrom tests
+}
+
+dependencies {
+ // please keep the list grouped by configuration and ordered by name
+
+ compile 'com.google.guava:guava'
+ compile project(':sonar-core')
+ compile project(path: ':sonar-plugin-api', configuration: 'shadow')
+ compile project(':sonar-plugin-api-impl')
+ compile project(':sonar-ws')
+
+ compileOnly 'com.google.code.findbugs:jsr305'
+ compileOnly 'javax.servlet:javax.servlet-api'
+ compileOnly 'org.apache.tomcat.embed:tomcat-embed-core'
+
+ testCompile 'com.google.code.findbugs:jsr305'
+ testCompile 'javax.servlet:javax.servlet-api'
+ testCompile 'org.apache.tomcat.embed:tomcat-embed-core'
+ testCompile 'org.mockito:mockito-core'
+ testCompile project(':sonar-testing-harness')
+}
+
+task testJar(type: Jar) {
+ classifier = 'tests'
+ from sourceSets.test.output
+}
+
+artifacts {
+ tests testJar
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import com.google.common.base.MoreObjects;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
+import static java.util.Arrays.asList;
+
+/**
+ * Request is not valid and can not be processed.
+ */
+public class BadRequestException extends ServerException {
+
+ private final transient List<String> errors;
+
+ private BadRequestException(List<String> errors) {
+ super(HTTP_BAD_REQUEST, errors.get(0));
+ this.errors = errors;
+ }
+
+ public static BadRequestException create(List<String> errorMessages) {
+ checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
+ checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
+ return new BadRequestException(errorMessages);
+ }
+
+ public static BadRequestException create(String... errorMessages) {
+ return create(asList(errorMessages));
+ }
+
+ public List<String> errors() {
+ return errors;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("errors", errors)
+ .toString();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import com.google.common.base.Preconditions;
+
+import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
+
+/**
+ * Permission denied. User does not have the required permissions.
+ */
+public class ForbiddenException extends ServerException {
+
+ public ForbiddenException(String message) {
+ super(HTTP_FORBIDDEN, Preconditions.checkNotNull(message));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import com.google.common.base.Preconditions;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.lang.String.format;
+
+public class Message {
+
+ private final String msg;
+
+ private Message(String format, Object... params) {
+ Preconditions.checkArgument(!isNullOrEmpty(format), "Message cannot be empty");
+ this.msg = format(format, params);
+ }
+
+ public String getMessage() {
+ return msg;
+ }
+
+ public static Message of(String msg, Object... arguments) {
+ return new Message(msg, arguments);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Message other = (Message) o;
+ return this.msg.equals(other.msg);
+ }
+
+ @Override
+ public int hashCode() {
+ return msg.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return msg;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+
+public class NotFoundException extends ServerException {
+
+ public NotFoundException(String message) {
+ super(HTTP_NOT_FOUND, message);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import static java.util.Objects.requireNonNull;
+
+public class ServerException extends RuntimeException {
+ private final int httpCode;
+
+ public ServerException(int httpCode, String message) {
+ super(requireNonNull(message, "Error message cannot be null"));
+ this.httpCode = httpCode;
+ }
+
+ public int httpCode() {
+ return httpCode;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.exceptions;
+
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+/**
+ * User needs to be authenticated. HTTP request is generally redirected to login form.
+ */
+public class UnauthorizedException extends ServerException {
+
+ public UnauthorizedException(String message) {
+ super(HTTP_UNAUTHORIZED, message);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.exceptions;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Writer that writes only when closing the resource
+ */
+class CacheWriter extends Writer {
+ private final StringWriter bufferWriter;
+ private final Writer outputWriter;
+ private boolean isClosed;
+
+ CacheWriter(Writer outputWriter) {
+ this.bufferWriter = new StringWriter();
+ this.outputWriter = outputWriter;
+ this.isClosed = false;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ bufferWriter.write(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() {
+ bufferWriter.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+
+ IOUtils.write(bufferWriter.toString(), outputWriter);
+ outputWriter.close();
+ this.isClosed = true;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ws;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.LocalConnector;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonarqube.ws.MediaTypes;
+
+public class DefaultLocalResponse implements Response, LocalConnector.LocalResponse {
+
+ private final InMemoryStream stream = new InMemoryStream();
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final Map<String, String> headers = Maps.newHashMap();
+
+ @Override
+ public int getStatus() {
+ return stream().status();
+ }
+
+ @Override
+ public String getMediaType() {
+ return stream().mediaType();
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return output.toByteArray();
+ }
+
+ public class InMemoryStream implements Response.Stream {
+ private String mediaType;
+
+ private int status = 200;
+
+ @CheckForNull
+ public String mediaType() {
+ return mediaType;
+ }
+
+ public int status() {
+ return status;
+ }
+
+ @Override
+ public Response.Stream setMediaType(String s) {
+ this.mediaType = s;
+ return this;
+ }
+
+ @Override
+ public Response.Stream setStatus(int i) {
+ this.status = i;
+ return this;
+ }
+
+ @Override
+ public OutputStream output() {
+ return output;
+ }
+
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ stream.setMediaType(MediaTypes.JSON);
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ stream.setMediaType(MediaTypes.XML);
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public InMemoryStream stream() {
+ return stream;
+ }
+
+ @Override
+ public Response noContent() {
+ stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT);
+ IOUtils.closeQuietly(output);
+ return this;
+ }
+
+ public String outputAsString() {
+ return new String(output.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public Response setHeader(String name, String value) {
+ headers.put(name, value);
+ return this;
+ }
+
+ @Override
+ public Collection<String> getHeaderNames() {
+ return headers.keySet();
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ public byte[] getFlushedOutput() {
+ try {
+ output.flush();
+ return output.toByteArray();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.util.Collection;
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.text.JsonWriter;
+
+public class JsonWriterUtils {
+
+ private JsonWriterUtils() {
+ // Utility class
+ }
+
+ public static void writeIfNeeded(JsonWriter json, @Nullable String value, String field, @Nullable Collection<String> fields) {
+ if (isFieldNeeded(field, fields)) {
+ json.prop(field, value);
+ }
+ }
+
+ public static void writeIfNeeded(JsonWriter json, @Nullable Boolean value, String field, @Nullable Collection<String> fields) {
+ if (isFieldNeeded(field, fields)) {
+ json.prop(field, value);
+ }
+ }
+
+ public static void writeIfNeeded(JsonWriter json, @Nullable Integer value, String field, @Nullable Collection<String> fields) {
+ if (isFieldNeeded(field, fields)) {
+ json.prop(field, value);
+ }
+ }
+
+ public static void writeIfNeeded(JsonWriter json, @Nullable Long value, String field, @Nullable Collection<String> fields) {
+ if (isFieldNeeded(field, fields)) {
+ json.prop(field, value);
+ }
+ }
+
+ public static void writeIfNeeded(JsonWriter json, @Nullable Date value, String field, @Nullable Collection<String> fields) {
+ if (isFieldNeeded(field, fields)) {
+ json.propDateTime(field, value);
+ }
+ }
+
+ public static boolean isFieldNeeded(String field, @Nullable Collection<String> fields) {
+ return fields == null || fields.isEmpty() || fields.contains(field);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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;
+
+public class KeyExamples {
+ public static final String KEY_FILE_EXAMPLE_001 = "my_project:/src/foo/Bar.php";
+ public static final String KEY_FILE_EXAMPLE_002 = "another_project:/src/foo/Foo.php";
+ public static final String KEY_PROJECT_EXAMPLE_001 = "my_project";
+ public static final String KEY_PROJECT_EXAMPLE_002 = "another_project";
+ public static final String KEY_PROJECT_EXAMPLE_003 = "third_project";
+
+ public static final String KEY_ORG_EXAMPLE_001 = "my-org";
+ public static final String KEY_ORG_EXAMPLE_002 = "foo-company";
+
+ public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch";
+ public static final String KEY_PULL_REQUEST_EXAMPLE_001 = "5461";
+
+ public static final String NAME_WEBHOOK_EXAMPLE_001 = "My Webhook";
+ public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar";
+
+ private KeyExamples() {
+ // prevent instantiation
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.api.server.ws.LocalConnector;
+import org.sonar.api.impl.ws.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
+ public Map<String, String[]> getParams() {
+ return localRequest.getParameterMap();
+ }
+
+ @Override
+ protected List<String> readMultiParam(String key) {
+ return localRequest.getMultiParam(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
+ protected Part readPart(String key) {
+ throw new UnsupportedOperationException("reading part is not supported yet by local WS calls");
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return localRequest.hasParam(key);
+ }
+
+ @Override
+ public String getPath() {
+ return localRequest.getPath();
+ }
+
+ @Override
+ public String method() {
+ return localRequest.getMethod();
+ }
+
+ @Override
+ public String getMediaType() {
+ return localRequest.getMediaType();
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return localRequest.getHeader(name);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.io.Resources;
+import java.net.URL;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.server.exceptions.ServerException;
+
+import static java.net.HttpURLConnection.HTTP_GONE;
+
+/**
+ * Used to declare web services that are removed
+ */
+public enum RemovedWebServiceHandler implements RequestHandler {
+
+ INSTANCE;
+
+ @Override
+ public void handle(Request request, Response response) {
+ throw new ServerException(HTTP_GONE, String.format("The web service '%s' doesn't exist anymore, please read its documentation to use alternatives", request.getPath()));
+ }
+
+ public URL getResponseExample() {
+ return Resources.getResource(RemovedWebServiceHandler.class, "removed-ws-example.json");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.exceptions.ServerException;
+
+import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
+
+public class RequestVerifier {
+ private RequestVerifier() {
+ // static methods only
+ }
+
+ public static void verifyRequest(WebService.Action action, Request request) {
+ switch (request.method()) {
+ case "GET":
+ if (action.isPost()) {
+ throw new ServerException(SC_METHOD_NOT_ALLOWED, "HTTP method POST is required");
+ }
+ return;
+ case "PUT":
+ case "DELETE":
+ throw new ServerException(SC_METHOD_NOT_ALLOWED, String.format("HTTP method %s is not allowed", request.method()));
+ default:
+ // Nothing to do
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+
+/**
+ * Used to declare web services that are implemented by a servlet filter.
+ */
+public class ServletFilterHandler implements RequestHandler {
+
+ public static final RequestHandler INSTANCE = new ServletFilterHandler();
+
+ private ServletFilterHandler() {
+ // Nothing
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ throw new UnsupportedOperationException("This web service is implemented as a servlet filter");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.net.HttpHeaders;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.servlet.http.HttpServletRequest;
+import org.sonar.api.impl.ws.PartImpl;
+import org.sonar.api.impl.ws.ValidatingRequest;
+import org.sonar.api.utils.log.Loggers;
+import org.sonarqube.ws.MediaTypes;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static java.util.Collections.emptyList;
+import static java.util.Locale.ENGLISH;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.apache.tomcat.util.http.fileupload.FileUploadBase.MULTIPART;
+
+public class ServletRequest extends ValidatingRequest {
+
+ private final HttpServletRequest source;
+
+ static final Map<String, String> SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX = ImmutableMap.of(
+ "json", MediaTypes.JSON,
+ "protobuf", MediaTypes.PROTOBUF,
+ "text", MediaTypes.TXT);
+
+ public ServletRequest(HttpServletRequest source) {
+ this.source = source;
+ }
+
+ @Override
+ public String method() {
+ return source.getMethod();
+ }
+
+ @Override
+ public String getMediaType() {
+ return firstNonNull(
+ mediaTypeFromUrl(source.getRequestURI()),
+ firstNonNull(
+ acceptedContentTypeInResponse(),
+ MediaTypes.DEFAULT));
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ try {
+ return source.getReader();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to read", e);
+ }
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return source.getParameterMap().containsKey(key);
+ }
+
+ @Override
+ public String readParam(String key) {
+ return source.getParameter(key);
+ }
+
+ @Override
+ public Map<String, String[]> getParams() {
+ return source.getParameterMap();
+ }
+
+ @Override
+ public List<String> readMultiParam(String key) {
+ String[] values = source.getParameterValues(key);
+ return values == null ? emptyList() : ImmutableList.copyOf(values);
+ }
+
+ @Override
+ protected InputStream readInputStreamParam(String key) {
+ Part part = readPart(key);
+ return (part == null) ? null : part.getInputStream();
+ }
+
+ @Override
+ @CheckForNull
+ public Part readPart(String key) {
+ try {
+ if (!isMultipartContent()) {
+ return null;
+ }
+ javax.servlet.http.Part part = source.getPart(key);
+ if (part == null || part.getSize() == 0) {
+ return null;
+ }
+ return new PartImpl(part.getInputStream(), part.getSubmittedFileName());
+ } catch (Exception e) {
+ Loggers.get(ServletRequest.class).warn("Can't read file part for parameter " + key, e);
+ return null;
+ }
+ }
+
+ private boolean isMultipartContent() {
+ String contentType = source.getContentType();
+ return contentType != null && contentType.toLowerCase(ENGLISH).startsWith(MULTIPART);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer url = source.getRequestURL();
+ String query = source.getQueryString();
+ if (query != null) {
+ url.append("?").append(query);
+ }
+ return url.toString();
+ }
+
+ @CheckForNull
+ private String acceptedContentTypeInResponse() {
+ return source.getHeader(HttpHeaders.ACCEPT);
+ }
+
+ @CheckForNull
+ private static String mediaTypeFromUrl(String url) {
+ String formatSuffix = substringAfterLast(url, ".");
+ return SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX.get(formatSuffix.toLowerCase(ENGLISH));
+ }
+
+ @Override
+ public String getPath() {
+ return source.getRequestURI().replaceFirst(source.getContextPath(), "");
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return Optional.ofNullable(source.getHeader(name));
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
+ Enumeration<String> headerNames = source.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ mapBuilder.put(headerName, source.getHeader(headerName));
+ }
+ return mapBuilder.build();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonarqube.ws.MediaTypes.JSON;
+import static org.sonarqube.ws.MediaTypes.XML;
+
+public class ServletResponse implements Response {
+
+ private final ServletStream stream;
+
+ public ServletResponse(HttpServletResponse response) {
+ stream = new ServletStream(response);
+ }
+
+ public static class ServletStream implements Stream {
+ private final HttpServletResponse response;
+
+ public ServletStream(HttpServletResponse response) {
+ this.response = response;
+ this.response.setStatus(200);
+ // SONAR-6964 WS should not be cached by browser
+ this.response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ }
+
+ @Override
+ public ServletStream setMediaType(String s) {
+ this.response.setContentType(s);
+ return this;
+ }
+
+ @Override
+ public ServletStream setStatus(int httpStatus) {
+ this.response.setStatus(httpStatus);
+ return this;
+ }
+
+ @Override
+ public OutputStream output() {
+ try {
+ return response.getOutputStream();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ HttpServletResponse response() {
+ return response;
+ }
+
+ public ServletStream reset() {
+ response.reset();
+ return this;
+ }
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ stream.setMediaType(JSON);
+ return JsonWriter.of(new CacheWriter(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8)));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ stream.setMediaType(XML);
+ return XmlWriter.of(new OutputStreamWriter(stream.output(), UTF_8));
+ }
+
+ @Override
+ public ServletStream stream() {
+ return stream;
+ }
+
+ @Override
+ public Response noContent() {
+ stream.setStatus(204);
+ return this;
+ }
+
+ @Override
+ public Response setHeader(String name, String value) {
+ stream.response().setHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public Collection<String> getHeaderNames() {
+ return stream.response().getHeaderNames();
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return stream.response().getHeader(name);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Locale;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.catalina.connector.ClientAbortException;
+import org.picocontainer.Startable;
+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.impl.ws.ValidatingRequest;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ServerException;
+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.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
+import static org.apache.commons.lang.StringUtils.substring;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.apache.commons.lang.StringUtils.substringBeforeLast;
+import static org.sonar.server.ws.RequestVerifier.verifyRequest;
+import static org.sonar.server.ws.ServletRequest.SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX;
+import static org.sonar.server.ws.WsUtils.checkFound;
+
+/**
+ * @since 4.2
+ */
+@ServerSide
+public class WebServiceEngine implements LocalConnector, Startable {
+
+ private static final Logger LOGGER = Loggers.get(WebServiceEngine.class);
+
+ private final WebService[] webServices;
+
+ private WebService.Context context;
+
+ public WebServiceEngine(WebService[] webServices) {
+ this.webServices = webServices;
+ }
+
+ @Override
+ public void start() {
+ context = new WebService.Context();
+ for (WebService webService : webServices) {
+ webService.define(context);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // nothing
+ }
+
+ private WebService.Context getContext() {
+ return requireNonNull(context, "Web services has not yet been initialized");
+ }
+
+ List<WebService.Controller> controllers() {
+ return getContext().controllers();
+ }
+
+ @Override
+ public LocalResponse call(LocalRequest request) {
+ DefaultLocalResponse localResponse = new DefaultLocalResponse();
+ execute(new LocalRequestAdapter(request), localResponse);
+ return localResponse;
+ }
+
+ public void execute(Request request, Response response) {
+ try {
+ ActionExtractor actionExtractor = new ActionExtractor(request.getPath());
+ WebService.Action action = getAction(actionExtractor);
+ checkFound(action, "Unknown url : %s", request.getPath());
+ if (request instanceof ValidatingRequest) {
+ ((ValidatingRequest) request).setAction(action);
+ ((ValidatingRequest) request).setLocalConnector(this);
+ }
+ checkActionExtension(actionExtractor.getExtension());
+ verifyRequest(action, request);
+ action.handler().handle(request, response);
+ } catch (IllegalArgumentException e) {
+ sendErrors(request, response, e, 400, singletonList(e.getMessage()));
+ } catch (BadRequestException e) {
+ sendErrors(request, response, e, 400, e.errors());
+ } catch (ServerException e) {
+ sendErrors(request, response, e, e.httpCode(), singletonList(e.getMessage()));
+ } catch (Exception e) {
+ sendErrors(request, response, e, 500, singletonList("An error has occurred. Please contact your administrator"));
+ }
+ }
+
+ @CheckForNull
+ private WebService.Action getAction(ActionExtractor actionExtractor) {
+ String controllerPath = actionExtractor.getController();
+ String actionKey = actionExtractor.getAction();
+ WebService.Controller controller = getContext().controller(controllerPath);
+ return controller == null ? null : controller.action(actionKey);
+ }
+
+ private static void sendErrors(Request request, Response response, Exception exception, int status, List<String> errors) {
+ if (isRequestAbortedByClient(exception)) {
+ // do not pollute logs. We can't do anything -> use DEBUG level
+ // see org.sonar.server.ws.ServletResponse#output()
+ LOGGER.debug(String.format("Request %s has been aborted by client", request), exception);
+ if (!isResponseCommitted(response)) {
+ // can be useful for access.log
+ response.stream().setStatus(299);
+ }
+ return;
+ }
+
+ if (status == 500) {
+ // Sending exception message into response is a vulnerability. Error must be
+ // displayed only in logs.
+ LOGGER.error("Fail to process request " + request, exception);
+ }
+
+ Response.Stream stream = response.stream();
+ if (isResponseCommitted(response)) {
+ // status can't be changed
+ LOGGER.debug(String.format("Request %s failed during response streaming", request), exception);
+ return;
+ }
+
+ // response is not committed, status and content can be changed to return the error
+ if (stream instanceof ServletResponse.ServletStream) {
+ ((ServletResponse.ServletStream) stream).reset();
+ }
+ stream.setStatus(status);
+ stream.setMediaType(MediaTypes.JSON);
+ try (JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output(), StandardCharsets.UTF_8))) {
+ json.beginObject();
+ writeErrors(json, errors);
+ json.endObject();
+ } catch (Exception e) {
+ // Do not hide the potential exception raised in the try block.
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private static boolean isRequestAbortedByClient(Exception exception) {
+ return Throwables.getCausalChain(exception).stream().anyMatch(t -> t instanceof ClientAbortException);
+ }
+
+ private static boolean isResponseCommitted(Response response) {
+ Response.Stream stream = response.stream();
+ // Request has been aborted by the client or the response was partially streamed, nothing can been done as Tomcat has committed the response
+ return stream instanceof ServletResponse.ServletStream && ((ServletResponse.ServletStream) stream).response().isCommitted();
+ }
+
+ public static void writeErrors(JsonWriter json, List<String> errorMessages) {
+ if (errorMessages.isEmpty()) {
+ return;
+ }
+ json.name("errors").beginArray();
+ errorMessages.forEach(message -> {
+ json.beginObject();
+ json.prop("msg", message);
+ json.endObject();
+ });
+ json.endArray();
+ }
+
+ private static void checkActionExtension(@Nullable String actionExtension) {
+ if (isNullOrEmpty(actionExtension)) {
+ return;
+ }
+ checkArgument(SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX.get(actionExtension.toLowerCase(Locale.ENGLISH)) != null, "Unknown action extension: %s", actionExtension);
+ }
+
+ private static class ActionExtractor {
+ private static final String SLASH = "/";
+ private static final String POINT = ".";
+
+ private final String controller;
+ private final String action;
+ private final String extension;
+ private final String path;
+
+ ActionExtractor(String path) {
+ this.path = path;
+ String pathWithoutExtension = substringBeforeLast(path, POINT);
+ this.controller = extractController(pathWithoutExtension);
+ this.action = substringAfterLast(pathWithoutExtension, SLASH);
+ checkArgument(!action.isEmpty(), "Url is incorrect : '%s'", path);
+ this.extension = substringAfterLast(path, POINT);
+ }
+
+ private static String extractController(String path) {
+ String controller = substringBeforeLast(path, SLASH);
+ if (controller.startsWith(SLASH)) {
+ return substring(controller, 1);
+ }
+ return controller;
+ }
+
+ String getController() {
+ return controller;
+ }
+
+ String getAction() {
+ return action;
+ }
+
+ @CheckForNull
+ String getExtension() {
+ return extension;
+ }
+
+ String getPath() {
+ return path;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 org.sonar.api.server.ws.Definable;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.WebService;
+
+/**
+ * Since 5.2, this interface is the base for Web Service marker interfaces
+ * Convention for naming implementations: <i>web_service_class_name</i>Action. ex: ProjectsWsAction, UsersWsAction
+ */
+public interface WsAction extends RequestHandler, Definable<WebService.NewController> {
+ // Marker interface
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import org.sonar.api.resources.ResourceType;
+import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.i18n.I18n;
+
+import static java.lang.String.format;
+
+public class WsParameterBuilder {
+ private static final String PARAM_QUALIFIER = "qualifier";
+ private static final String PARAM_QUALIFIERS = "qualifiers";
+
+ private WsParameterBuilder() {
+ // static methods only
+ }
+
+ public static WebService.NewParam createRootQualifierParameter(WebService.NewAction action, QualifierParameterContext context) {
+ return action.createParam(PARAM_QUALIFIER)
+ .setDescription("Project qualifier. Filter the results with the specified qualifier. Possible values are:" + buildRootQualifiersDescription(context))
+ .setPossibleValues(getRootQualifiers(context.getResourceTypes()));
+ }
+
+ public static WebService.NewParam createRootQualifiersParameter(WebService.NewAction action, QualifierParameterContext context) {
+ return action.createParam(PARAM_QUALIFIERS)
+ .setDescription("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. " +
+ "Possible values are:" + buildRootQualifiersDescription(context))
+ .setPossibleValues(getRootQualifiers(context.getResourceTypes()));
+ }
+
+ public static WebService.NewParam createDefaultTemplateQualifierParameter(WebService.NewAction action, QualifierParameterContext context) {
+ return action.createParam(PARAM_QUALIFIER)
+ .setDescription("Project qualifier. Filter the results with the specified qualifier. Possible values are:" + buildDefaultTemplateQualifiersDescription(context))
+ .setPossibleValues(getDefaultTemplateQualifiers(context.getResourceTypes()));
+ }
+
+ public static WebService.NewParam createQualifiersParameter(WebService.NewAction action, QualifierParameterContext context) {
+ return action.createParam(PARAM_QUALIFIERS)
+ .setDescription(
+ "Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:" + buildAllQualifiersDescription(context))
+ .setPossibleValues(getAllQualifiers(context.getResourceTypes()));
+ }
+
+ private static Set<String> getRootQualifiers(ResourceTypes resourceTypes) {
+ return resourceTypes.getRoots().stream()
+ .map(ResourceType::getQualifier)
+ .collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ private static Set<String> getDefaultTemplateQualifiers(ResourceTypes resourceTypes) {
+ return resourceTypes.getRoots().stream()
+ .map(ResourceType::getQualifier)
+ .collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ private static Set<String> getAllQualifiers(ResourceTypes resourceTypes) {
+ return resourceTypes.getAll().stream()
+ .map(ResourceType::getQualifier)
+ .collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ private static String buildDefaultTemplateQualifiersDescription(QualifierParameterContext context) {
+ return buildQualifiersDescription(context, getDefaultTemplateQualifiers(context.getResourceTypes()));
+ }
+
+ private static String buildRootQualifiersDescription(QualifierParameterContext context) {
+ return buildQualifiersDescription(context, getRootQualifiers(context.getResourceTypes()));
+ }
+
+ private static String buildAllQualifiersDescription(QualifierParameterContext context) {
+ return buildQualifiersDescription(context, getAllQualifiers(context.getResourceTypes()));
+ }
+
+ private static String buildQualifiersDescription(QualifierParameterContext context, Set<String> qualifiers) {
+ StringBuilder description = new StringBuilder();
+ description.append("<ul>");
+ String qualifierPattern = "<li>%s - %s</li>";
+ for (String qualifier : qualifiers) {
+ description.append(format(qualifierPattern, qualifier, qualifierLabel(context, qualifier)));
+ }
+ description.append("</ul>");
+
+ return description.toString();
+ }
+
+ private static String qualifierLabel(QualifierParameterContext context, String qualifier) {
+ String qualifiersPropertyPrefix = "qualifiers.";
+ return context.getI18n().message(Locale.ENGLISH, qualifiersPropertyPrefix + qualifier, "no description available");
+ }
+
+ public static class QualifierParameterContext {
+ private final I18n i18n;
+ private final ResourceTypes resourceTypes;
+
+ private QualifierParameterContext(I18n i18n, ResourceTypes resourceTypes) {
+ this.i18n = i18n;
+ this.resourceTypes = resourceTypes;
+ }
+
+ public static QualifierParameterContext newQualifierParameterContext(I18n i18n, ResourceTypes resourceTypes) {
+ return new QualifierParameterContext(i18n, resourceTypes);
+ }
+
+ public I18n getI18n() {
+ return i18n;
+ }
+
+ public ResourceTypes getResourceTypes() {
+ return resourceTypes;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.protobuf.Message;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.util.ProtobufJsonFormat;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonarqube.ws.MediaTypes.JSON;
+import static org.sonarqube.ws.MediaTypes.PROTOBUF;
+
+public class WsUtils {
+
+ private WsUtils() {
+ // only statics
+ }
+
+ public static void writeProtobuf(Message msg, Request request, Response response) {
+ OutputStream output = response.stream().output();
+ try {
+ if (request.getMediaType().equals(PROTOBUF)) {
+ response.stream().setMediaType(PROTOBUF);
+ msg.writeTo(output);
+ } else {
+ response.stream().setMediaType(JSON);
+ try (JsonWriter writer = JsonWriter.of(new OutputStreamWriter(output, UTF_8))) {
+ ProtobufJsonFormat.write(msg, writer);
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Error while writing protobuf message", e);
+ } finally {
+ IOUtils.closeQuietly(output);
+ }
+ }
+
+ /**
+ * @throws BadRequestException
+ */
+ public static void checkRequest(boolean expression, String message, Object... messageArguments) {
+ if (!expression) {
+ throw BadRequestException.create(format(message, messageArguments));
+ }
+ }
+
+ public static void checkRequest(boolean expression, List<String> messages) {
+ if (!expression) {
+ throw BadRequestException.create(messages);
+ }
+ }
+
+ /**
+ * @throws NotFoundException if the value if null
+ * @return the value
+ */
+ public static <T> T checkFound(@Nullable T value, String message, Object... messageArguments) {
+ if (value == null) {
+ throw new NotFoundException(format(message, messageArguments));
+ }
+
+ return value;
+ }
+
+ /**
+ * @throws NotFoundException if the value is not present
+ * @return the value
+ */
+ public static <T> T checkFoundWithOptional(Optional<T> value, String message, Object... messageArguments) {
+ if (!value.isPresent()) {
+ throw new NotFoundException(format(message, messageArguments));
+ }
+
+ return value.get();
+ }
+
+ public static <T> T checkFoundWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
+ if (!value.isPresent()) {
+ throw new NotFoundException(format(message, messageArguments));
+ }
+
+ return value.get();
+ }
+
+ public static <T> T checkStateWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
+ if (!value.isPresent()) {
+ throw new IllegalStateException(format(message, messageArguments));
+ }
+
+ return value.get();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+{
+ "errors": [
+ {
+ "msg": "The web service '/api/...' doesn't exists anymore, please read its documentation to use alternatives"
+ }
+ ]
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class CacheWriterTest {
+ private Writer writer = new StringWriter();
+ private CacheWriter underTest = new CacheWriter(writer);
+
+ @Test
+ public void write_content_when_closing_resource() throws IOException {
+ underTest.write("content");
+ assertThat(writer.toString()).isEmpty();
+
+ underTest.close();
+
+ assertThat(writer.toString()).isEqualTo("content");
+ }
+
+ @Test
+ public void close_encapsulated_writer_once() throws IOException {
+ writer = mock(Writer.class);
+ underTest = new CacheWriter(writer);
+
+ underTest.close();
+ underTest.close();
+
+ verify(writer, times(1)).close();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.Response;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+
+public class DumbResponse implements Response {
+ private InMemoryStream stream;
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ private Map<String, String> headers = Maps.newHashMap();
+
+ 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;
+ }
+
+ public String outputAsString() {
+ return new String(output.toByteArray(), StandardCharsets.UTF_8);
+ }
+ }
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public InMemoryStream stream() {
+ if (stream == null) {
+ stream = new InMemoryStream();
+ }
+ 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;
+ }
+
+ public Collection<String> getHeaderNames() {
+ return headers.keySet();
+ }
+
+ @CheckForNull
+ public String getHeader(String name){
+ return headers.get(name);
+ }
+
+ public byte[] getFlushedOutput() {
+ try {
+ output.flush();
+ return output.toByteArray();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.Request;
+import org.sonar.server.exceptions.ServerException;
+
+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 RemovedWebServiceHandlerTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void throw_server_exception() throws Exception {
+ Request request = mock(Request.class);
+ when(request.getPath()).thenReturn("/api/resources/index");
+
+ try {
+ RemovedWebServiceHandler.INSTANCE.handle(request, null);
+ fail();
+ } catch (ServerException e) {
+ assertThat(e.getMessage()).isEqualTo("The web service '/api/resources/index' doesn't exist anymore, please read its documentation to use alternatives");
+ assertThat(e.httpCode()).isEqualTo(410);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.collect.ImmutableMap;
+import com.google.common.net.HttpHeaders;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.Part;
+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.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ServletRequestTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private HttpServletRequest source = mock(HttpServletRequest.class);
+
+ private ServletRequest underTest = new ServletRequest(source);
+
+ @Test
+ public void call_method() {
+ underTest.method();
+
+ verify(source).getMethod();
+ }
+
+ @Test
+ public void getMediaType() {
+ when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
+ when(source.getRequestURI()).thenReturn("/path/to/resource/search");
+
+ assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.JSON);
+ }
+
+ @Test
+ public void default_media_type_is_octet_stream() {
+ when(source.getRequestURI()).thenReturn("/path/to/resource/search");
+
+ assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.DEFAULT);
+ }
+
+ @Test
+ public void media_type_taken_in_url_first() {
+ when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
+ when(source.getRequestURI()).thenReturn("/path/to/resource/search.protobuf");
+
+ assertThat(underTest.getMediaType()).isEqualTo(MediaTypes.PROTOBUF);
+ }
+
+ @Test
+ public void has_param_from_source() {
+ when(source.getParameterMap()).thenReturn(ImmutableMap.of("param", new String[] {"value"}));
+ ServletRequest request = new ServletRequest(source);
+ assertThat(request.hasParam("param")).isTrue();
+ }
+
+ @Test
+ public void read_param_from_source() {
+ when(source.getParameter("param")).thenReturn("value");
+
+ assertThat(underTest.readParam("param")).isEqualTo("value");
+ }
+
+ @Test
+ public void read_multi_param_from_source_with_values() {
+ when(source.getParameterValues("param")).thenReturn(new String[]{"firstValue", "secondValue", "thirdValue"});
+
+ List<String> result = underTest.readMultiParam("param");
+
+ assertThat(result).containsExactly("firstValue", "secondValue", "thirdValue");
+ }
+
+ @Test
+ public void read_multi_param_from_source_with_one_value() {
+ when(source.getParameterValues("param")).thenReturn(new String[]{"firstValue"});
+
+ List<String> result = underTest.readMultiParam("param");
+
+ assertThat(result).containsExactly("firstValue");
+ }
+
+ @Test
+ public void read_multi_param_from_source_without_value() {
+ when(source.getParameterValues("param")).thenReturn(null);
+
+ List<String> result = underTest.readMultiParam("param");
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void read_input_stream() throws Exception {
+ when(source.getContentType()).thenReturn("multipart/form-data");
+ InputStream file = mock(InputStream.class);
+ Part part = mock(Part.class);
+ when(part.getInputStream()).thenReturn(file);
+ when(part.getSize()).thenReturn(10L);
+ when(source.getPart("param1")).thenReturn(part);
+
+ assertThat(underTest.readInputStreamParam("param1")).isEqualTo(file);
+ assertThat(underTest.readInputStreamParam("param2")).isNull();
+ }
+
+ @Test
+ public void read_no_input_stream_when_part_size_is_zero() throws Exception {
+ when(source.getContentType()).thenReturn("multipart/form-data");
+ InputStream file = mock(InputStream.class);
+ Part part = mock(Part.class);
+ when(part.getInputStream()).thenReturn(file);
+ when(part.getSize()).thenReturn(0L);
+ when(source.getPart("param1")).thenReturn(part);
+
+ assertThat(underTest.readInputStreamParam("param1")).isNull();
+ }
+
+ @Test
+ public void return_no_input_stream_when_content_type_is_not_multipart() {
+ when(source.getContentType()).thenReturn("multipart/form-data");
+
+ assertThat(underTest.readInputStreamParam("param1")).isNull();
+ }
+
+ @Test
+ public void return_no_input_stream_when_content_type_is_null() {
+ when(source.getContentType()).thenReturn(null);
+
+ assertThat(underTest.readInputStreamParam("param1")).isNull();
+ }
+
+ @Test
+ public void returns_null_when_invalid_part() throws Exception {
+ when(source.getContentType()).thenReturn("multipart/form-data");
+ InputStream file = mock(InputStream.class);
+ Part part = mock(Part.class);
+ when(part.getSize()).thenReturn(0L);
+ when(part.getInputStream()).thenReturn(file);
+ doThrow(IllegalArgumentException.class).when(source).getPart("param1");
+
+ assertThat(underTest.readInputStreamParam("param1")).isNull();
+ }
+
+ @Test
+ public void getPath() {
+ when(source.getRequestURI()).thenReturn("/sonar/path/to/resource/search");
+ when(source.getContextPath()).thenReturn("/sonar");
+
+ assertThat(underTest.getPath()).isEqualTo("/path/to/resource/search");
+ }
+
+ @Test
+ public void to_string() {
+ when(source.getRequestURL()).thenReturn(new StringBuffer("http:localhost:9000/api/issues"));
+ assertThat(underTest.toString()).isEqualTo("http:localhost:9000/api/issues");
+
+ when(source.getQueryString()).thenReturn("components=sonar");
+
+ assertThat(underTest.toString()).isEqualTo("http:localhost:9000/api/issues?components=sonar");
+ }
+
+ @Test
+ public void header_returns_the_value_of_http_header() {
+ when(source.getHeader("Accept")).thenReturn("text/plain");
+ assertThat(underTest.header("Accept")).hasValue("text/plain");
+ }
+
+ @Test
+ public void header_is_empty_if_absent_from_request() {
+ when(source.getHeader("Accept")).thenReturn(null);
+ assertThat(underTest.header("Accept")).isEmpty();
+ }
+
+ @Test
+ public void header_has_empty_value_if_present_in_request_without_value() {
+ when(source.getHeader("Accept")).thenReturn("");
+ assertThat(underTest.header("Accept")).hasValue("");
+ }
+
+ @Test
+ public void getReader() throws IOException {
+ BufferedReader reader = new BufferedReader(new StringReader("foo"));
+ when(source.getReader()).thenReturn(reader);
+
+ assertThat(underTest.getReader()).isEqualTo(reader);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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 javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonarqube.ws.MediaTypes.JSON;
+import static org.sonarqube.ws.MediaTypes.XML;
+
+public class ServletResponseTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private ServletOutputStream output = mock(ServletOutputStream.class);
+ private HttpServletResponse response = mock(HttpServletResponse.class);
+
+ private ServletResponse underTest = new ServletResponse(response);
+
+ @Before
+ public void setUp() throws Exception {
+ when(response.getOutputStream()).thenReturn(output);
+ }
+
+ @Test
+ public void test_default_header() {
+ verify(response).setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ }
+
+ @Test
+ public void set_header() {
+ underTest.setHeader("header", "value");
+
+ verify(response).setHeader("header", "value");
+ }
+
+ @Test
+ public void get_header() {
+ underTest.getHeader("header");
+
+ verify(response).getHeader("header");
+ }
+
+ @Test
+ public void get_header_names() {
+ underTest.getHeaderNames();
+
+ verify(response).getHeaderNames();
+ }
+
+ @Test
+ public void test_default_status() {
+ verify(response).setStatus(200);
+ }
+
+ @Test
+ public void set_status() {
+ underTest.stream().setStatus(404);
+
+ verify(response).setStatus(404);
+ }
+
+ @Test
+ public void test_output() {
+ assertThat(underTest.stream().output()).isEqualTo(output);
+ }
+
+
+ @Test
+ public void test_reset() {
+ underTest.stream().reset();
+
+ verify(response).reset();
+ }
+
+ @Test
+ public void test_newJsonWriter() throws Exception {
+ underTest.newJsonWriter();
+
+ verify(response).setContentType(JSON);
+ verify(response).getOutputStream();
+ }
+
+ @Test
+ public void test_newXmlWriter() throws Exception {
+ underTest.newXmlWriter();
+
+ verify(response).setContentType(XML);
+ verify(response).getOutputStream();
+ }
+
+ @Test
+ public void test_noContent() throws Exception {
+ underTest.noContent();
+
+ verify(response).setStatus(204);
+ verify(response, never()).getOutputStream();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.protobuf.GeneratedMessageV3;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.impl.ws.PartImpl;
+import org.sonar.api.impl.ws.ValidatingRequest;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.sonarqube.ws.MediaTypes.PROTOBUF;
+
+public class TestRequest extends ValidatingRequest {
+
+ private final ListMultimap<String, String> multiParams = ArrayListMultimap.create();
+ private final Map<String, String> params = new HashMap<>();
+ private final Map<String, String> headers = new HashMap<>();
+ private final Map<String, Part> parts = Maps.newHashMap();
+ private String payload = "";
+ private boolean payloadConsumed = false;
+ private String method = "GET";
+ private String mimeType = "application/octet-stream";
+ private String path;
+
+ @Override
+ public BufferedReader getReader() {
+ checkState(!payloadConsumed, "Payload already consumed");
+ if (payload == null) {
+ return super.getReader();
+ }
+
+ BufferedReader res = new BufferedReader(new StringReader(payload));
+ payloadConsumed = true;
+ return res;
+ }
+
+ public TestRequest setPayload(String payload) {
+ checkState(!payloadConsumed, "Payload already consumed");
+
+ this.payload = payload;
+ return this;
+ }
+
+ @Override
+ protected String readParam(String key) {
+ return params.get(key);
+ }
+
+ @Override
+ protected List<String> readMultiParam(String key) {
+ return multiParams.get(key);
+ }
+
+ @Override
+ protected InputStream readInputStreamParam(String key) {
+ String value = readParam(key);
+ if (value == null) {
+ return null;
+ }
+ return IOUtils.toInputStream(value);
+ }
+
+ @Override
+ protected Part readPart(String key) {
+ return parts.get(key);
+ }
+
+ public TestRequest setPart(String key, InputStream input, String fileName) {
+ parts.put(key, new PartImpl(input, fileName));
+ return this;
+ }
+
+ @Override
+ public String method() {
+ return method;
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return params.containsKey(key) || multiParams.containsKey(key);
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public Map<String, String[]> getParams() {
+ ArrayListMultimap<String, String> result = ArrayListMultimap.create(multiParams);
+ params.forEach(result::put);
+ return result.asMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toArray(new String[0])));
+ }
+
+ public TestRequest setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public TestRequest setMethod(String method) {
+ checkNotNull(method);
+ this.method = method;
+ return this;
+ }
+
+ @Override
+ public String getMediaType() {
+ return mimeType;
+ }
+
+ public TestRequest setMediaType(String type) {
+ checkNotNull(type);
+ this.mimeType = type;
+ return this;
+ }
+
+ public TestRequest setParam(String key, String value) {
+ checkNotNull(key);
+ checkNotNull(value);
+ this.params.put(key, value);
+ return this;
+ }
+
+ public TestRequest setMultiParam(String key, List<String> values) {
+ requireNonNull(key);
+ requireNonNull(values);
+
+ multiParams.putAll(key, values);
+
+ return this;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ return ImmutableMap.copyOf(headers);
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return Optional.ofNullable(headers.get(name));
+ }
+
+ public TestRequest setHeader(String name, String value) {
+ headers.put(requireNonNull(name), requireNonNull(value));
+ return this;
+ }
+
+ public TestResponse execute() {
+ try {
+ DumbResponse response = new DumbResponse();
+ action().handler().handle(this, response);
+ return new TestResponse(response);
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ public <T extends GeneratedMessageV3> T executeProtobuf(Class<T> protobufClass) {
+ return setMediaType(PROTOBUF).execute().getInputObject(protobufClass);
+ }
+
+ @Override
+ public String toString() {
+ return path;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.protobuf.GeneratedMessageV3;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import javax.annotation.CheckForNull;
+import org.sonar.test.JsonAssert;
+
+public class TestResponse {
+
+ private final DumbResponse dumbResponse;
+
+ TestResponse(DumbResponse dumbResponse) {
+ this.dumbResponse = dumbResponse;
+ }
+
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(dumbResponse.getFlushedOutput());
+ }
+
+ public <T extends GeneratedMessageV3> T getInputObject(Class<T> protobufClass) {
+ try (InputStream input = getInputStream()) {
+ Method parseFromMethod = protobufClass.getMethod("parseFrom", InputStream.class);
+ @SuppressWarnings("unchecked")
+ T result = (T) parseFromMethod.invoke(null, input);
+ return result;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public String getInput() {
+ return new String(dumbResponse.getFlushedOutput(), StandardCharsets.UTF_8);
+ }
+
+ public String getMediaType() {
+ return dumbResponse.stream().mediaType();
+ }
+
+ public int getStatus() {
+ return dumbResponse.stream().status();
+ }
+
+ @CheckForNull
+ public String getHeader(String headerKey) {
+ return dumbResponse.getHeader(headerKey);
+ }
+
+ public void assertJson(String expectedJson) {
+ JsonAssert.assertJson(getInput()).isSimilarTo(expectedJson);
+ }
+
+ /**
+ * Compares JSON response with JSON file available in classpath. For example if class
+ * is org.foo.BarTest and filename is index.json, then file must be located
+ * at src/test/resources/org/foo/BarTest/index.json.
+ *
+ * @param clazz the test class
+ * @param expectedJsonFilename name of the file containing the expected JSON
+ */
+ public void assertJson(Class clazz, String expectedJsonFilename) {
+ String path = clazz.getSimpleName() + "/" + expectedJsonFilename;
+ URL url = clazz.getResource(path);
+ if (url == null) {
+ throw new IllegalStateException("Cannot find " + path);
+ }
+ JsonAssert.assertJson(getInput()).isSimilarTo(url);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.util.function.Consumer;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.catalina.connector.ClientAbortException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+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.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonarqube.ws.MediaTypes;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.apache.commons.lang.StringUtils.substringBeforeLast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class WebServiceEngineTest {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void load_ws_definitions_at_startup() {
+ WebServiceEngine underTest = new WebServiceEngine(new WebService[] {
+ newWs("api/foo/index", a -> {
+ }),
+ newWs("api/bar/index", a -> {
+ })
+ });
+ underTest.start();
+ try {
+ assertThat(underTest.controllers())
+ .extracting(WebService.Controller::path)
+ .containsExactlyInAnyOrder("api/foo", "api/bar");
+ } finally {
+ underTest.stop();
+ }
+ }
+
+ @Test
+ public void ws_returns_successful_response() {
+ Request request = new TestRequest().setPath("/api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("pong");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void accept_path_that_does_not_start_with_slash() {
+ Request request = new TestRequest().setPath("api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("pong");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void request_path_can_contain_valid_media_type() {
+ Request request = new TestRequest().setPath("api/ping.json");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("pong");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void bad_request_if_action_suffix_is_not_supported() {
+ Request request = new TestRequest().setPath("/api/ping.bat");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown action extension: bat\"}]}");
+ }
+
+ @Test
+ public void test_response_with_no_content() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ RequestHandler handler = (req, resp) -> resp.noContent();
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler(handler)));
+
+ assertThat(response.stream().outputAsString()).isEmpty();
+ assertThat(response.stream().status()).isEqualTo(204);
+ }
+
+ @Test
+ public void return_404_if_controller_does_not_exist() {
+ Request request = new TestRequest().setPath("xxx/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : xxx/ping\"}]}");
+ assertThat(response.stream().status()).isEqualTo(404);
+ }
+
+ @Test
+ public void return_404_if_action_does_not_exist() {
+ Request request = new TestRequest().setPath("api/xxx");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Unknown url : api/xxx\"}]}");
+ assertThat(response.stream().status()).isEqualTo(404);
+ }
+
+ @Test
+ public void fail_if_method_GET_is_not_allowed() {
+ Request request = new TestRequest().setMethod("GET").setPath("api/foo");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setPost(true)));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method POST is required\"}]}");
+ assertThat(response.stream().status()).isEqualTo(405);
+ }
+
+ @Test
+ public void POST_is_considered_as_GET_if_POST_is_not_supported() {
+ Request request = new TestRequest().setMethod("POST").setPath("api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("pong");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void method_PUT_is_not_allowed() {
+ Request request = new TestRequest().setMethod("PUT").setPath("/api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method PUT is not allowed\"}]}");
+ assertThat(response.stream().status()).isEqualTo(405);
+ }
+
+ @Test
+ public void method_DELETE_is_not_allowed() {
+ Request request = new TestRequest().setMethod("DELETE").setPath("api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> {
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"HTTP method DELETE is not allowed\"}]}");
+ assertThat(response.stream().status()).isEqualTo(405);
+ }
+
+ @Test
+ public void method_POST_is_required() {
+ Request request = new TestRequest().setMethod("POST").setPath("api/ping");
+
+ DumbResponse response = run(request, newPingWs(a -> a.setPost(true)));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("pong");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void fail_if_reading_an_undefined_parameter() {
+ Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> request.param("unknown"))));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"BUG - parameter 'unknown' is undefined for action 'foo'\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ }
+
+ @Test
+ public void fail_if_request_does_not_have_required_parameter() {
+ Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> {
+ a.createParam("bar").setRequired(true);
+ a.setHandler((req, resp) -> request.mandatoryParam("bar"));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The 'bar' parameter is missing\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ }
+
+ @Test
+ public void fail_if_request_does_not_have_required_parameter_even_if_handler_does_not_require_it() {
+ Request request = new TestRequest().setPath("api/foo").setParam("unknown", "Unknown");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> {
+ a.createParam("bar").setRequired(true);
+ // do not use mandatoryParam("bar")
+ a.setHandler((req, resp) -> request.param("bar"));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"The 'bar' parameter is missing\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ }
+
+ @Test
+ public void use_default_value_of_optional_parameter() {
+ Request request = new TestRequest().setPath("api/print");
+
+ DumbResponse response = run(request, newWs("api/print", a -> {
+ a.createParam("message").setDefaultValue("hello");
+ a.setHandler((req, resp) -> resp.stream().output().write(req.param("message").getBytes(UTF_8)));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("hello");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void use_request_parameter_on_parameter_with_default_value() {
+ Request request = new TestRequest().setPath("api/print").setParam("message", "bar");
+
+ DumbResponse response = run(request, newWs("api/print", a -> {
+ a.createParam("message").setDefaultValue("default_value");
+ a.setHandler((req, resp) -> resp.stream().output().write(req.param("message").getBytes(UTF_8)));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("bar");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void accept_parameter_value_within_defined_possible_values() {
+ Request request = new TestRequest().setPath("api/foo").setParam("format", "json");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> {
+ a.createParam("format").setPossibleValues("json", "xml");
+ a.setHandler((req, resp) -> resp.stream().output().write(req.mandatoryParam("format").getBytes(UTF_8)));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("json");
+ assertThat(response.stream().status()).isEqualTo(200);
+ }
+
+ @Test
+ public void fail_if_parameter_value_is_not_in_defined_possible_values() {
+ Request request = new TestRequest().setPath("api/foo").setParam("format", "yml");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> {
+ a.createParam("format").setPossibleValues("json", "xml");
+ a.setHandler((req, resp) -> resp.stream().output().write(req.mandatoryParam("format").getBytes(UTF_8)));
+ }));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"Value of parameter 'format' (yml) must be one of: [json, xml]\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ }
+
+ @Test
+ public void return_500_on_internal_error() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ DumbResponse response = run(request, newFailWs());
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"An error has occurred. Please contact your administrator\"}]}");
+ assertThat(response.stream().status()).isEqualTo(500);
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(logTester.logs(LoggerLevel.ERROR)).filteredOn(l -> l.contains("Fail to process request api/foo")).isNotEmpty();
+ }
+
+ @Test
+ public void return_400_on_BadRequestException_with_single_message() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
+ throw BadRequestException.create("Bad request !");
+ })));
+
+ assertThat(response.stream().outputAsString()).isEqualTo(
+ "{\"errors\":[{\"msg\":\"Bad request !\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+ }
+
+ @Test
+ public void return_400_on_BadRequestException_with_multiple_messages() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
+ throw BadRequestException.create("one", "two", "three");
+ })));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":["
+ + "{\"msg\":\"one\"},"
+ + "{\"msg\":\"two\"},"
+ + "{\"msg\":\"three\"}"
+ + "]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+ }
+
+ @Test
+ public void return_error_message_containing_character_percent() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> {
+ throw new IllegalArgumentException("this should not fail %s");
+ })));
+
+ assertThat(response.stream().outputAsString()).isEqualTo("{\"errors\":[{\"msg\":\"this should not fail %s\"}]}");
+ assertThat(response.stream().status()).isEqualTo(400);
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ }
+
+ @Test
+ public void send_response_headers() {
+ Request request = new TestRequest().setPath("api/foo");
+
+ DumbResponse response = run(request, newWs("api/foo", a -> a.setHandler((req, resp) -> resp.setHeader("Content-Disposition", "attachment; filename=foo.zip"))));
+
+ assertThat(response.getHeader("Content-Disposition")).isEqualTo("attachment; filename=foo.zip");
+ }
+
+ @Test
+ public void support_aborted_request_when_response_is_already_committed() {
+ Request request = new TestRequest().setPath("api/foo");
+ Response response = mockServletResponse(true);
+
+ run(request, response, newClientAbortWs());
+
+ // response is committed (status is already sent), so status can't be changed
+ verify(response.stream(), never()).setStatus(anyInt());
+
+ assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Request api/foo has been aborted by client");
+ }
+
+ @Test
+ public void support_aborted_request_when_response_is_not_committed() {
+ Request request = new TestRequest().setPath("api/foo");
+ Response response = mockServletResponse(false);
+
+ run(request, response, newClientAbortWs());
+
+ verify(response.stream()).setStatus(299);
+ assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Request api/foo has been aborted by client");
+ }
+
+ @Test
+ public void internal_error_when_response_is_already_committed() {
+ Request request = new TestRequest().setPath("api/foo");
+ Response response = mockServletResponse(true);
+
+ run(request, response, newFailWs());
+
+ // response is committed (status is already sent), so status can't be changed
+ verify(response.stream(), never()).setStatus(anyInt());
+ assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request api/foo");
+ }
+
+ @Test
+ public void internal_error_when_response_is_not_committed() {
+ Request request = new TestRequest().setPath("api/foo");
+ Response response = mockServletResponse(false);
+
+ run(request, response, newFailWs());
+
+ verify(response.stream()).setStatus(500);
+ assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request api/foo");
+ }
+
+ @Test
+ public void fail_when_start_in_not_called() {
+ Request request = new TestRequest().setPath("/api/ping");
+ DumbResponse response = new DumbResponse();
+ WebServiceEngine underTest = new WebServiceEngine(new WebService[] {newPingWs(a -> {
+ })});
+
+ underTest.execute(request, response);
+
+ assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Fail to process request /api/ping");
+ }
+
+ private static WebService newWs(String path, Consumer<WebService.NewAction> consumer) {
+ return context -> {
+ WebService.NewController controller = context.createController(substringBeforeLast(path, "/"));
+ WebService.NewAction action = createNewDefaultAction(controller, substringAfterLast(path, "/"));
+ action.setHandler((request, response) -> {
+ });
+ consumer.accept(action);
+ controller.done();
+ };
+ }
+
+ private static WebService newPingWs(Consumer<WebService.NewAction> consumer) {
+ return newWs("api/ping", a -> {
+ a.setHandler((request, response) -> response.stream().output().write("pong".getBytes(UTF_8)));
+ consumer.accept(a);
+ });
+ }
+
+ private static WebService newFailWs() {
+ return newWs("api/foo", a -> a.setHandler((req, resp) -> {
+ throw new IllegalStateException("BOOM");
+ }));
+ }
+
+ private static DumbResponse run(Request request, WebService... webServices) {
+ DumbResponse response = new DumbResponse();
+ return (DumbResponse) run(request, response, webServices);
+ }
+
+ private static Response run(Request request, Response response, WebService... webServices) {
+ WebServiceEngine underTest = new WebServiceEngine(webServices);
+ underTest.start();
+ try {
+ underTest.execute(request, response);
+ return response;
+ } finally {
+ underTest.stop();
+ }
+ }
+
+ private static Response mockServletResponse(boolean committed) {
+ Response response = mock(Response.class, Mockito.RETURNS_DEEP_STUBS);
+ ServletResponse.ServletStream servletStream = mock(ServletResponse.ServletStream.class, Mockito.RETURNS_DEEP_STUBS);
+ when(response.stream()).thenReturn(servletStream);
+ HttpServletResponse httpServletResponse = mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS);
+ when(httpServletResponse.isCommitted()).thenReturn(committed);
+ when(servletStream.response()).thenReturn(httpServletResponse);
+ return response;
+ }
+
+ private static WebService newClientAbortWs() {
+ return newWs("api/foo", a -> a.setHandler((req, resp) -> {
+ throw new ClientAbortException();
+ }));
+ }
+
+ private static WebService.NewAction createNewDefaultAction(WebService.NewController controller, String key) {
+ return controller
+ .createAction(key)
+ .setDescription("Dummy Description")
+ .setSince("5.3")
+ .setResponseExample(WebServiceEngineTest.class.getResource("web-service-engine-test.txt"));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.collect.Iterables;
+import org.sonar.api.server.ws.WebService;
+
+public class WsActionTester {
+
+ public static final String CONTROLLER_KEY = "test";
+ private final WebService.Action action;
+
+ public WsActionTester(WsAction wsAction) {
+ WebService.Context context = new WebService.Context();
+ WebService.NewController newController = context.createController(CONTROLLER_KEY);
+ wsAction.define(newController);
+ newController.done();
+ action = Iterables.get(context.controller(CONTROLLER_KEY).actions(), 0);
+ }
+
+ public WebService.Action getDef() {
+ return action;
+ }
+
+ public TestRequest newRequest() {
+ TestRequest request = new TestRequest();
+ request.setAction(action);
+ return request;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.collect.Maps;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.impl.ws.PartImpl;
+import org.sonar.api.impl.ws.ValidatingRequest;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonar.server.ws.WsTester.TestResponse.TestStream;
+import org.sonar.test.JsonAssert;
+import org.sonarqube.ws.MediaTypes;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.ws.RequestVerifier.verifyRequest;
+
+/**
+ * @since 4.2
+ * @deprecated use {@link WsActionTester} in preference to this class
+ */
+@Deprecated
+public class WsTester {
+
+ public static class TestRequest extends ValidatingRequest {
+
+ private final String method;
+ private String path;
+ private String mediaType = MediaTypes.JSON;
+
+ private Map<String, String> params = Maps.newHashMap();
+ private Map<String, String> headers = Maps.newHashMap();
+ private final Map<String, Part> parts = Maps.newHashMap();
+
+ private TestRequest(String method) {
+ this.method = method;
+ }
+
+ @Override
+ public String method() {
+ return method;
+ }
+
+ @Override
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ public TestRequest setMediaType(String s) {
+ this.mediaType = s;
+ return this;
+ }
+
+ @Override
+ public boolean hasParam(String key) {
+ return params.keySet().contains(key);
+ }
+
+ @Override
+ public Optional<String> header(String name) {
+ return Optional.ofNullable(headers.get(name));
+ }
+
+ public TestRequest setHeader(String name, String value) {
+ this.headers.put(requireNonNull(name), requireNonNull(value));
+ return this;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ public TestRequest setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public TestRequest setParams(Map<String, String> m) {
+ this.params = m;
+ return this;
+ }
+
+ public TestRequest setParam(String key, @Nullable String value) {
+ if (value != null) {
+ params.put(key, value);
+ }
+ return this;
+ }
+
+ @Override
+ protected String readParam(String key) {
+ return params.get(key);
+ }
+
+ @Override
+ protected List<String> readMultiParam(String key) {
+ String value = params.get(key);
+ return value == null ? emptyList() : singletonList(value);
+ }
+
+ @Override
+ public Map<String, String[]> getParams() {
+ return params.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new String[] {e.getValue()}));
+ }
+
+ @Override
+ protected InputStream readInputStreamParam(String key) {
+ String param = readParam(key);
+
+ return param == null ? null : IOUtils.toInputStream(param);
+ }
+
+ @Override
+ protected Part readPart(String key) {
+ return parts.get(key);
+ }
+
+ public TestRequest setPart(String key, InputStream input, String fileName) {
+ parts.put(key, new PartImpl(input, fileName));
+ return this;
+ }
+
+ public Result execute() throws Exception {
+ TestResponse response = new TestResponse();
+ verifyRequest(action(), this);
+ action().handler().handle(this, response);
+ return new Result(response);
+ }
+ }
+
+ public static class TestResponse implements Response {
+
+ private TestStream stream;
+
+ private Map<String, String> headers = Maps.newHashMap();
+
+ public class TestStream implements Response.Stream {
+ private String mediaType;
+ private int status;
+
+ @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;
+ }
+ }
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ @Override
+ public JsonWriter newJsonWriter() {
+ return JsonWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public XmlWriter newXmlWriter() {
+ return XmlWriter.of(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public Stream stream() {
+ if (stream == null) {
+ stream = new TestStream();
+ }
+ 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 static class Result {
+ private final TestResponse response;
+
+ private Result(TestResponse response) {
+ this.response = response;
+ }
+
+ public Result assertNoContent() {
+ return assertStatus(HttpURLConnection.HTTP_NO_CONTENT);
+ }
+
+ public String outputAsString() {
+ return new String(response.output.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ public byte[] output() {
+ return response.output.toByteArray();
+ }
+
+ public Result assertJson(String expectedJson) {
+ String json = outputAsString();
+ JsonAssert.assertJson(json).isSimilarTo(expectedJson);
+ return this;
+ }
+
+ /**
+ * Compares JSON response with JSON file available in classpath. For example if class
+ * is org.foo.BarTest and filename is index.json, then file must be located
+ * at src/test/resources/org/foo/BarTest/index.json.
+ *
+ * @param clazz the test class
+ * @param expectedJsonFilename name of the file containing the expected JSON
+ */
+ public Result assertJson(Class clazz, String expectedJsonFilename) {
+ String path = clazz.getSimpleName() + "/" + expectedJsonFilename;
+ URL url = clazz.getResource(path);
+ if (url == null) {
+ throw new IllegalStateException("Cannot find " + path);
+ }
+ String json = outputAsString();
+ JsonAssert.assertJson(json).isSimilarTo(url);
+ return this;
+ }
+
+ public Result assertNotModified() {
+ return assertStatus(HttpURLConnection.HTTP_NOT_MODIFIED);
+ }
+
+ public Result assertStatus(int httpStatus) {
+ assertThat(((TestStream) response.stream()).status()).isEqualTo(httpStatus);
+ return this;
+ }
+
+ public Result assertHeader(String name, String value) {
+ assertThat(response.getHeader(name)).isEqualTo(value);
+ return this;
+ }
+ }
+
+ private final WebService.Context context = new WebService.Context();
+
+ public WsTester(WebService... webServices) {
+ for (WebService webService : webServices) {
+ webService.define(context);
+ }
+ }
+
+ public WebService.Context context() {
+ return context;
+ }
+
+ @CheckForNull
+ public WebService.Controller controller(String key) {
+ return context.controller(key);
+ }
+
+ @CheckForNull
+ public WebService.Action action(String controllerKey, String actionKey) {
+ WebService.Controller controller = context.controller(controllerKey);
+ if (controller != null) {
+ return controller.action(actionKey);
+ }
+ return null;
+ }
+
+ public TestRequest newGetRequest(String controllerKey, String actionKey) {
+ return newRequest(controllerKey, actionKey, "GET");
+ }
+
+ public TestRequest newPostRequest(String controllerKey, String actionKey) {
+ return newRequest(controllerKey, actionKey, "POST");
+ }
+
+ private TestRequest newRequest(String controllerKey, String actionKey, String method) {
+ TestRequest request = new TestRequest(method);
+ WebService.Controller controller = context.controller(controllerKey);
+ if (controller == null) {
+ throw new IllegalArgumentException(
+ String.format("Controller '%s' is unknown, did you forget to call NewController.done()?", controllerKey));
+ }
+ WebService.Action action = controller.action(actionKey);
+ if (action == null) {
+ throw new IllegalArgumentException(
+ String.format("Action '%s' not found on controller '%s'.", actionKey, controllerKey));
+ }
+ request.setAction(action);
+ return request;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info 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.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonarqube.ws.Issues;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.Permissions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.ExceptionCauseMatcher.hasType;
+
+public class WsUtilsTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public LogTester logger = new LogTester();
+
+ @Test
+ public void write_json_by_default() {
+ TestRequest request = new TestRequest();
+ DumbResponse response = new DumbResponse();
+
+ Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
+ WsUtils.writeProtobuf(msg, request, response);
+
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.JSON);
+ assertThat(response.outputAsString())
+ .startsWith("{")
+ .contains("\"key\":\"I1\"")
+ .endsWith("}");
+ }
+
+ @Test
+ public void write_protobuf() throws Exception {
+ TestRequest request = new TestRequest();
+ request.setMediaType(MediaTypes.PROTOBUF);
+ DumbResponse response = new DumbResponse();
+
+ Issues.Issue msg = Issues.Issue.newBuilder().setKey("I1").build();
+ WsUtils.writeProtobuf(msg, request, response);
+
+ assertThat(response.stream().mediaType()).isEqualTo(MediaTypes.PROTOBUF);
+ assertThat(Issues.Issue.parseFrom(response.getFlushedOutput()).getKey()).isEqualTo("I1");
+ }
+
+ @Test
+ public void rethrow_error_as_ISE_when_error_writing_message() {
+ TestRequest request = new TestRequest();
+ request.setMediaType(MediaTypes.PROTOBUF);
+
+ Permissions.Permission message = Permissions.Permission.newBuilder().setName("permission-name").build();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectCause(hasType(NullPointerException.class));
+ expectedException.expectMessage("Error while writing protobuf message");
+ // provoke NullPointerException
+ WsUtils.writeProtobuf(message, null, new DumbResponse());
+ }
+
+ @Test
+ public void checkRequest_ok() {
+ WsUtils.checkRequest(true, "Missing param: %s", "foo");
+ // do not fail
+ }
+
+ @Test
+ public void checkRequest_ko() {
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Missing param: foo");
+
+ WsUtils.checkRequest(false, "Missing param: %s", "foo");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration debug="false">
+ <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>
+ %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+ <logger name="ch.qos.logback">
+ <level value="WARN"/>
+ </logger>
+
+ <logger name="okhttp3.mockwebserver">
+ <level value="WARN"/>
+ </logger>
+
+</configuration>
include 'server:sonar-server-common'
include 'server:sonar-vsts'
include 'server:sonar-web'
+include 'server:sonar-webserver-ws'
include 'sonar-application'
include 'sonar-check-api'