diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-01-23 18:12:30 +0100 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-01-24 18:36:49 +0100 |
commit | 17cbda842774f2331d65a53cd645ba2dcd2d154a (patch) | |
tree | da8494229f2834ccb9b920b7822dc63ef45959f3 | |
parent | 4fc7e70ddb24c863a2686b954676790adf071aa7 (diff) | |
download | sonarqube-17cbda842774f2331d65a53cd645ba2dcd2d154a.tar.gz sonarqube-17cbda842774f2331d65a53cd645ba2dcd2d154a.zip |
SONAR-7298 Deprecate and rewrite WS api/projects/index in Java
23 files changed, 529 insertions, 152 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/IndexAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/IndexAction.java new file mode 100644 index 00000000000..fb9ea3c316e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/IndexAction.java @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.project.ws; + +import com.google.common.io.Resources; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +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.utils.text.JsonWriter; +import org.sonar.core.util.stream.Collectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.user.UserSession; + +import static java.util.Optional.ofNullable; +import static org.sonar.api.web.UserRole.USER; +import static org.sonar.core.util.stream.Collectors.toList; +import static org.sonar.core.util.stream.Collectors.uniqueIndex; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_INDEX; + +/** + * This web service is used by old version of SonarLint. + */ +public class IndexAction implements ProjectsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_SEARCH = "search"; + private static final String PARAM_SUB_PROJECTS = "subprojects"; + private static final String PARAM_FORMAT = "format"; + + private final DbClient dbClient; + private final UserSession userSession; + + public IndexAction(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(ACTION_INDEX) + .setDescription("This web service is deprecated, please use api/components/search instead") + .setSince("2.10") + .setDeprecatedSince("6.3") + .setHandler(this) + .setResponseExample(Resources.getResource(this.getClass(), "index-example.json")); + action.createParam(PARAM_KEY) + .setDescription("key or id of the project") + .setExampleValue(KEY_PROJECT_EXAMPLE_001); + action.createParam(PARAM_SEARCH) + .setDescription("Substring of project name, case insensitive. Ignored if the parameter key is set") + .setExampleValue("Sonar"); + action.createParam(PARAM_SUB_PROJECTS) + .setDescription("Load sub-projects. Ignored if the parameter key is set") + .setDefaultValue("false") + .setBooleanPossibleValues(); + action.createParam(PARAM_FORMAT) + .setDescription("Only json response format is available") + .setPossibleValues("json"); + addRemovedParameter("desc", action); + addRemovedParameter("views", action); + addRemovedParameter("libs", action); + addRemovedParameter("versions", action); + } + + @Override + public void handle(Request request, Response response) throws Exception { + try (DbSession dbSession = dbClient.openSession(false)) { + List<ComponentDto> projects = getAuthorizedProjects(dbSession, searchProjects(dbSession, request)); + JsonWriter json = response.newJsonWriter(); + json.beginArray(); + for (ComponentDto project : projects) { + addProject(json, project); + } + json.endArray(); + json.close(); + } + } + + private Optional<ComponentDto> getProjectByKeyOrId(DbSession dbSession, String component) { + try { + Long componentId = Long.parseLong(component); + return ofNullable(dbClient.componentDao().selectById(dbSession, componentId).orNull()); + } catch (NumberFormatException e) { + return ofNullable(dbClient.componentDao().selectByKey(dbSession, component).orNull()); + } + } + + private List<ComponentDto> searchProjects(DbSession dbSession, Request request) { + String projectKey = request.param(PARAM_KEY); + List<ComponentDto> projects = new ArrayList<>(); + if (projectKey != null) { + getProjectByKeyOrId(dbSession, projectKey).ifPresent(projects::add); + } else { + String nameQuery = request.param(PARAM_SEARCH); + boolean includeModules = request.paramAsBoolean(PARAM_SUB_PROJECTS); + projects.addAll(dbClient.componentDao().selectProjectsByNameQuery(dbSession, nameQuery, includeModules)); + } + return projects; + } + + private List<ComponentDto> getAuthorizedProjects(DbSession dbSession, List<ComponentDto> projectDtos) { + if (projectDtos.isEmpty()) { + return Collections.emptyList(); + } + Map<String, Long> projectIdsByUuids = projectDtos.stream().collect(uniqueIndex(ComponentDto::uuid, ComponentDto::getId)); + Set<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(dbSession, + projectDtos.stream().map(ComponentDto::getId).collect(toList()), + userSession.getUserId(), USER); + return projectDtos.stream() + .filter(c -> authorizedProjectIds.contains(projectIdsByUuids.get(c.projectUuid()))) + .collect(Collectors.toList()); + } + + private static void addProject(JsonWriter json, ComponentDto project) { + json.beginObject() + .prop("id", project.getId()) + .prop("k", project.getKey()) + .prop("nm", project.name()) + .prop("sc", project.scope()) + .prop("qu", project.qualifier()) + .endObject(); + } + + private static void addRemovedParameter(String key, WebService.NewAction action) { + action.createParam(key) + .setDescription("Since 6.3, this parameter has no effect") + .setDeprecatedKey("6.3"); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java index e6a42102996..b15f7362dac 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java @@ -19,17 +19,12 @@ */ package org.sonar.server.project.ws; -import com.google.common.io.Resources; import java.util.Arrays; -import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; -import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER; public class ProjectsWs implements WebService { - public static final String ENDPOINT = "api/projects"; - private static final String FALSE = "false"; - private static final String TRUE = "true"; private final ProjectsWsAction[] actions; @@ -39,56 +34,11 @@ public class ProjectsWs implements WebService { @Override public void define(Context context) { - NewController controller = context.createController(ENDPOINT) + NewController controller = context.createController(CONTROLLER) .setSince("2.10") .setDescription("Manage project existence."); - - defineIndexAction(controller); Arrays.stream(actions).forEach(action -> action.define(controller)); controller.done(); } - private void defineIndexAction(NewController controller) { - WebService.NewAction action = controller.createAction("index") - .setDescription("Search for projects") - .setSince("2.10") - .setHandler(RailsHandler.INSTANCE) - .setResponseExample(Resources.getResource(this.getClass(), "projects-example-index.json")); - - action.createParam("key") - .setDescription("id or key of the project") - .setExampleValue(KEY_PROJECT_EXAMPLE_001); - - action.createParam("search") - .setDescription("Substring of project name, case insensitive") - .setExampleValue("Sonar"); - - action.createParam("desc") - .setDescription("Load project description") - .setDefaultValue(TRUE) - .setBooleanPossibleValues(); - - action.createParam("subprojects") - .setDescription("Load sub-projects. Ignored if the parameter key is set") - .setDefaultValue(FALSE) - .setBooleanPossibleValues(); - - action.createParam("views") - .setDescription("Load views and sub-views. Ignored if the parameter key is set") - .setDefaultValue(FALSE) - .setBooleanPossibleValues(); - - action.createParam("libs") - .setDescription("Load libraries. Ignored if the parameter key is set") - .setDefaultValue(FALSE) - .setBooleanPossibleValues(); - - action.createParam("versions") - .setDescription("Load version") - .setDefaultValue(FALSE) - .setPossibleValues(TRUE, FALSE, "last"); - - RailsHandler.addFormatParam(action); - } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index e3e5138bf55..8a06f08e34a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -27,6 +27,7 @@ public class ProjectsWsModule extends Module { add( ProjectsWs.class, CreateAction.class, + IndexAction.class, BulkDeleteAction.class, DeleteAction.class, GhostsAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/property/ws/IndexAction.java b/server/sonar-server/src/main/java/org/sonar/server/property/ws/IndexAction.java index c4205ff3f54..cdeb026538b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/property/ws/IndexAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/property/ws/IndexAction.java @@ -49,8 +49,6 @@ import org.sonar.server.ws.WsAction; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.sonar.api.PropertyType.PROPERTY_SET; -import static org.sonar.api.server.ws.RailsHandler.PARAM_FORMAT; -import static org.sonar.api.server.ws.RailsHandler.addJsonOnlyFormatParam; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.server.setting.ws.SettingsPermissionPredicates.DOT_LICENSE; import static org.sonar.server.setting.ws.SettingsPermissionPredicates.DOT_SECURED; @@ -64,6 +62,7 @@ public class IndexAction implements WsAction { public static final String PARAM_ID = "id"; public static final String PARAM_COMPONENT = "resource"; + public static final String PARAM_FORMAT = "format"; private final DbClient dbClient; private final UserSession userSession; @@ -89,7 +88,9 @@ public class IndexAction implements WsAction { action.createParam(PARAM_COMPONENT) .setDescription("Component key or database id") .setExampleValue(KEY_PROJECT_EXAMPLE_001); - addJsonOnlyFormatParam(action); + action.createParam(PARAM_FORMAT) + .setDescription("Only json response format is available") + .setPossibleValues("json"); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/DeprecatedRestWebServiceFilter.java b/server/sonar-server/src/main/java/org/sonar/server/ws/DeprecatedRestWebServiceFilter.java index c3749bd9dc7..2e54e06f42a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/DeprecatedRestWebServiceFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/DeprecatedRestWebServiceFilter.java @@ -42,7 +42,6 @@ import org.sonar.server.property.ws.IndexAction; import static com.google.common.base.Strings.isNullOrEmpty; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.sonar.api.server.ws.RailsHandler.PARAM_FORMAT; import static org.sonar.server.property.ws.PropertiesWs.CONTROLLER_PROPERTIES; import static org.sonarqube.ws.client.setting.SettingsWsParameters.ACTION_RESET; import static org.sonarqube.ws.client.setting.SettingsWsParameters.ACTION_SET; @@ -172,7 +171,7 @@ public class DeprecatedRestWebServiceFilter extends ServletFilter { private void handleGet(Optional<String> key, Optional<String> component) { addParameterIfPresent(IndexAction.PARAM_ID, key); addParameterIfPresent(IndexAction.PARAM_COMPONENT, component); - addParameterIfPresent(PARAM_FORMAT, readParam(PARAM_FORMAT)); + addParameterIfPresent(IndexAction.PARAM_FORMAT, readParam(IndexAction.PARAM_FORMAT)); redirectedPath = CONTROLLER_PROPERTIES + "/index"; redirectedMethod = "GET"; } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/projects-example-index.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/index-example.json index a5d82ab23c3..a5d82ab23c3 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/projects-example-index.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/index-example.json diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java index 247cd7a1856..52c0c754664 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java @@ -71,6 +71,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.server.project.ws.DeleteAction.PARAM_ID; import static org.sonar.server.project.ws.DeleteAction.PARAM_KEY; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER; public class DeleteActionTest { @@ -278,6 +279,6 @@ public class DeleteActionTest { } private WsTester.TestRequest newRequest() { - return ws.newPostRequest(ProjectsWs.ENDPOINT, ACTION); + return ws.newPostRequest(CONTROLLER, ACTION); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/IndexActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/IndexActionTest.java new file mode 100644 index 00000000000..a3be3e099bd --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/IndexActionTest.java @@ -0,0 +1,199 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.project.ws; + +import java.util.Arrays; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.db.component.ComponentTesting.newModuleDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.test.JsonAssert.assertJson; + +public class IndexActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = db.getDbClient(); + + private UserDto user; + + private WsActionTester ws = new WsActionTester(new IndexAction(dbClient, userSession)); + + @Before + public void setUp() { + user = db.users().insertUser("john"); + userSession.login(user); + } + + @Test + public void search_all_projects() throws Exception { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(null, null, null); + + verifyResult(result, "search_projects.json"); + } + + @Test + public void search_projects_with_modules() throws Exception { + ComponentDto project1 = newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"); + ComponentDto project2 = newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"); + insertProjectsAuthorizedForUser(project1, project2); + db.components().insertComponents( + newModuleDto(project1).setKey("org.jenkins-ci.plugins:sonar-common").setName("Common"), + newModuleDto(project2).setKey("org.codehaus.sonar-plugins:sonar-ant-db").setName("Ant DB")); + + String result = call(null, null, true); + + verifyResult(result, "search_projects_with_modules.json"); + } + + @Test + public void search_project_by_key() throws Exception { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call("org.jenkins-ci.plugins:sonar", null, null); + + verifyResult(result, "search_project_by_key.json"); + } + + @Test + public void search_project_by_id() throws Exception { + ComponentDto project = newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"); + insertProjectsAuthorizedForUser( + project, + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(Long.toString(project.getId()), null, null); + + verifyResult(result, "search_project_by_id.json"); + } + + @Test + public void search_project_by_name() throws Exception { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(null, "Plu", null); + + verifyResult(result, "search_project_by_name.json"); + } + + @Test + public void return_empty_list_when_no_project_match_search() throws Exception { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(null, "Unknown", null); + + verifyResult(result, "empty.json"); + } + + @Test + public void return_only_projects_authorized_for_user() throws Exception { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task")); + db.components() + .insertComponent(newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(null, null, null); + + verifyResult(result, "return_only_projects_authorized_for_user.json"); + } + + @Test + public void test_example() { + insertProjectsAuthorizedForUser( + newProjectDto(db.getDefaultOrganization()).setKey("org.jenkins-ci.plugins:sonar").setName("Jenkins Sonar Plugin"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-ant-task").setName("Sonar Ant Task"), + newProjectDto(db.getDefaultOrganization()).setKey("org.codehaus.sonar-plugins:sonar-build-breaker-plugin").setName("Sonar Build Breaker Plugin")); + + String result = call(null, null, null); + + assertJson(result).ignoreFields("id").isSimilarTo(ws.getDef().responseExampleAsString()); + } + + @Test + public void define_index_action() { + WebService.Action action = ws.getDef(); + assertThat(action).isNotNull(); + assertThat(action.responseExampleAsString()).isNotEmpty(); + assertThat(action.params()).hasSize(8); + } + + private String call(@Nullable String key, @Nullable String search, @Nullable Boolean subprojects) { + TestRequest httpRequest = ws.newRequest(); + setNullable(key, e -> httpRequest.setParam("key", e)); + setNullable(search, e -> httpRequest.setParam("search", e)); + setNullable(subprojects, e -> httpRequest.setParam("subprojects", Boolean.toString(e))); + return httpRequest.execute().getInput(); + } + + private void insertProjectsAuthorizedForUser(ComponentDto... projects) { + db.components().insertComponents(projects); + setBrowsePermissionOnUser(projects); + db.commit(); + } + + private void setBrowsePermissionOnUser(ComponentDto... projects) { + Arrays.stream(projects).forEach(project -> db.users().insertProjectPermissionOnUser(user, UserRole.USER, project)); + db.getSession().commit(); + } + + private void verifyResult(String json, String expectedJsonFile) { + assertJson(json).ignoreFields("id").isSimilarTo(getClass().getResource(getClass().getSimpleName() + "/" + expectedJsonFile)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java index c2a2dab4b74..87215ece5d2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java @@ -29,6 +29,6 @@ public class ProjectsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new ProjectsWsModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 8); + assertThat(container.size()).isEqualTo(2 + 9); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsTest.java deleted file mode 100644 index 214e9f176e4..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.project.ws; - -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.server.ws.RailsHandler; -import org.sonar.api.server.ws.WebService; -import org.sonar.db.DbClient; -import org.sonar.server.component.ComponentCleanerService; -import org.sonar.server.user.UserSession; -import org.sonar.server.ws.WsTester; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class ProjectsWsTest { - - WebService.Controller controller; - WsTester ws; - - @Before - public void setUp() { - ws = new WsTester(new ProjectsWs( - new BulkDeleteAction(mock(ComponentCleanerService.class), mock(DbClient.class), mock(UserSession.class)), - new GhostsAction(mock(DbClient.class), mock(UserSession.class)), - new ProvisionedAction(mock(DbClient.class), mock(UserSession.class)))); - controller = ws.controller("api/projects"); - } - - @Test - public void define_controller() { - assertThat(controller).isNotNull(); - assertThat(controller.description()).isNotEmpty(); - assertThat(controller.since()).isEqualTo("2.10"); - assertThat(controller.actions()).hasSize(4); - } - - @Test - public void define_index_action() { - WebService.Action action = controller.action("index"); - assertThat(action).isNotNull(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); - assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(8); - } - -} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/empty.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/empty.json new file mode 100644 index 00000000000..41b42e677b9 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/empty.json @@ -0,0 +1,3 @@ +[ + +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/return_only_projects_authorized_for_user.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/return_only_projects_authorized_for_user.json new file mode 100644 index 00000000000..9bc8440f9b0 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/return_only_projects_authorized_for_user.json @@ -0,0 +1,16 @@ +[ + { + "id": 4, + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + }, + { + "id": 5, + "k": "org.codehaus.sonar-plugins:sonar-ant-task", + "nm": "Sonar Ant Task", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_id.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_id.json new file mode 100644 index 00000000000..4c9d99ac462 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_id.json @@ -0,0 +1,9 @@ +[ + { + "id": "5035", + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_key.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_key.json new file mode 100644 index 00000000000..4c9d99ac462 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_key.json @@ -0,0 +1,9 @@ +[ + { + "id": "5035", + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_name.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_name.json new file mode 100644 index 00000000000..bd36e6832c3 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_project_by_name.json @@ -0,0 +1,16 @@ +[ + { + "id": 7, + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + }, + { + "id": 9, + "k": "org.codehaus.sonar-plugins:sonar-build-breaker-plugin", + "nm": "Sonar Build Breaker Plugin", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects.json new file mode 100644 index 00000000000..a5d82ab23c3 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects.json @@ -0,0 +1,23 @@ +[ + { + "id": "5035", + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + }, + { + "id": "5146", + "k": "org.codehaus.sonar-plugins:sonar-ant-task", + "nm": "Sonar Ant Task", + "sc": "PRJ", + "qu": "TRK" + }, + { + "id": "15964", + "k": "org.codehaus.sonar-plugins:sonar-build-breaker-plugin", + "nm": "Sonar Build Breaker Plugin", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects_with_modules.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects_with_modules.json new file mode 100644 index 00000000000..51b0c2f4872 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/IndexActionTest/search_projects_with_modules.json @@ -0,0 +1,30 @@ +[ + { + "id": 16, + "k": "org.codehaus.sonar-plugins:sonar-ant-db", + "nm": "Ant DB", + "sc": "PRJ", + "qu": "BRC" + }, + { + "id": 15, + "k": "org.jenkins-ci.plugins:sonar-common", + "nm": "Common", + "sc": "PRJ", + "qu": "BRC" + }, + { + "id": 13, + "k": "org.jenkins-ci.plugins:sonar", + "nm": "Jenkins Sonar Plugin", + "sc": "PRJ", + "qu": "TRK" + }, + { + "id": 14, + "k": "org.codehaus.sonar-plugins:sonar-ant-task", + "nm": "Sonar Ant Task", + "sc": "PRJ", + "qu": "TRK" + } +] diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java index e4d2527af9b..2d892055a79 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java @@ -39,15 +39,16 @@ import org.sonar.db.Dao; import org.sonar.db.DatabaseUtils; import org.sonar.db.DbSession; import org.sonar.db.RowNotFoundException; -import org.sonar.db.WildcardPosition; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Maps.newHashMapWithExpectedSize; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang.StringUtils.isBlank; +import static org.sonar.db.DatabaseUtils.buildLikeValue; import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeUpdates; +import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER; public class ComponentDao implements Dao { @@ -239,7 +240,7 @@ public class ComponentDao implements Dao { if (isBlank(textQuery)) { return null; } - return DatabaseUtils.buildLikeValue(textQuery.toUpperCase(Locale.ENGLISH), WildcardPosition.BEFORE_AND_AFTER); + return DatabaseUtils.buildLikeValue(textQuery.toUpperCase(Locale.ENGLISH), BEFORE_AND_AFTER); } public List<ComponentDto> selectGhostProjects(DbSession session, int offset, int limit, @Nullable String query) { @@ -288,6 +289,11 @@ public class ComponentDao implements Dao { return new HashSet<>(mapper(dbSession).selectComponentsByQualifiers(qualifiers)); } + public List<ComponentDto> selectProjectsByNameQuery(DbSession dbSession, @Nullable String nameQuery, boolean includeModules) { + String nameQueryForSql = nameQuery == null ? null : buildLikeValue(nameQuery, BEFORE_AND_AFTER).toUpperCase(Locale.ENGLISH); + return mapper(dbSession).selectProjectsByNameQuery(nameQueryForSql, includeModules); + } + private static void addPartialQueryParameterIfNotNull(Map<String, Object> parameters, @Nullable String keyOrNameFilter) { // TODO rely on resource_index table and match exactly the key if (keyOrNameFilter != null) { diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java index 101ac0f26c6..7cf6f163b49 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -117,6 +117,8 @@ public interface ComponentMapper { List<ComponentDto> selectComponentsHavingSameKeyOrderedById(String key); + List<ComponentDto> selectProjectsByNameQuery(@Param("nameQuery") @Nullable String nameQuery, @Param("includeModules") boolean includeModules); + long countGhostProjects(Map<String, Object> parameters); void selectForIndexing(@Param("projectUuid") @Nullable String projectUuid, ResultHandler handler); diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml index 5813d2aca3b..3ff8987b017 100644 --- a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -425,6 +425,26 @@ </if> </select> + <select id="selectProjectsByNameQuery" resultType="Component"> + select + <include refid="componentColumns"/> + from projects p + <where> + p.enabled=${_true} + AND p.copy_component_uuid is null + <if test="includeModules == false"> + AND p.qualifier = 'TRK' + </if> + <if test="includeModules == true"> + AND (p.qualifier = 'TRK' OR p.qualifier = 'BRC') + </if> + <if test="nameQuery != null"> + AND UPPER(p.name) like #{nameQuery,jdbcType=VARCHAR} + </if> + </where> + ORDER BY p.name + </select> + <insert id="insert" parameterType="Component" keyColumn="id" useGeneratedKeys="true" keyProperty="id"> INSERT INTO projects ( organization_uuid, diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java index 9bbaaf20aa7..e0b62a5dc03 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java @@ -720,9 +720,9 @@ public class ComponentDaoTest { @Test public void updateBEnabledToFalse() { - ComponentDto dto1 = ComponentTesting.newProjectDto(db.getDefaultOrganization(), "U1"); - ComponentDto dto2 = ComponentTesting.newProjectDto(db.getDefaultOrganization(), "U2"); - ComponentDto dto3 = ComponentTesting.newProjectDto(db.getDefaultOrganization(), "U3"); + ComponentDto dto1 = newProjectDto(db.getDefaultOrganization(), "U1"); + ComponentDto dto2 = newProjectDto(db.getDefaultOrganization(), "U2"); + ComponentDto dto3 = newProjectDto(db.getDefaultOrganization(), "U3"); underTest.insert(dbSession, dto1, dto2, dto3); underTest.updateBEnabledToFalse(dbSession, asList("U1", "U2")); @@ -1001,6 +1001,28 @@ public class ComponentDaoTest { assertThat(components).extracting("organizationUuid").containsOnly(organizationDto.getUuid()); } + @Test + public void select_projects_by_name_query() { + OrganizationDto organizationDto = db.organizations().insert(); + ComponentDto project1 = db.components().insertComponent(newProjectDto(organizationDto).setName("project1")); + ComponentDto module1 = db.components().insertComponent(newModuleDto(project1).setName("module1")); + ComponentDto subModule1 = db.components().insertComponent(newModuleDto(module1).setName("subModule1")); + ComponentDto file = db.components().insertComponent(newFileDto(subModule1).setName("file")); + ComponentDto project2 = db.components().insertComponent(newProjectDto(organizationDto).setName("project2")); + ComponentDto project3 = db.components().insertComponent(newProjectDto(organizationDto).setName("project3")); + + assertThat(underTest.selectProjectsByNameQuery(dbSession, null, false)).extracting(ComponentDto::uuid) + .containsOnly(project1.uuid(), project2.uuid(), project3.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, null, true)).extracting(ComponentDto::uuid) + .containsOnly(project1.uuid(), project2.uuid(), project3.uuid(), module1.uuid(), subModule1.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "project1", false)).extracting(ComponentDto::uuid).containsOnly(project1.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "ct1", false)).extracting(ComponentDto::uuid).containsOnly(project1.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "pro", false)).extracting(ComponentDto::uuid).containsOnly(project1.uuid(), project2.uuid(), project3.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "jec", false)).extracting(ComponentDto::uuid).containsOnly(project1.uuid(), project2.uuid(), project3.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "1", true)).extracting(ComponentDto::uuid).containsOnly(project1.uuid(), module1.uuid(), subModule1.uuid()); + assertThat(underTest.selectProjectsByNameQuery(dbSession, "unknown", true)).extracting(ComponentDto::uuid).isEmpty(); + } + private static ComponentTreeQuery.Builder newTreeQuery(String baseUuid) { return ComponentTreeQuery.builder() .setBaseUuid(baseUuid) diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RailsHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RailsHandler.java index 5c86c340ecf..63ce01080fb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RailsHandler.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RailsHandler.java @@ -27,7 +27,6 @@ package org.sonar.api.server.ws; public class RailsHandler implements RequestHandler { public static final RequestHandler INSTANCE = new RailsHandler(); - public static final String PARAM_FORMAT = "format"; private RailsHandler() { // Nothing @@ -38,26 +37,4 @@ public class RailsHandler implements RequestHandler { throw new UnsupportedOperationException("This web service is implemented in rails"); } - public static WebService.NewParam addFormatParam(WebService.NewAction action) { - return action.createParam(PARAM_FORMAT) - .setDescription("Response format can be set through:" + - "<ul>" + - "<li>Parameter format: xml | json</li>" + - "<li>Or the 'Accept' property in the HTTP header:" + - "<ul>" + - "<li>Accept:text/xml</li>" + - "<li>Accept:application/json</li>" + - "</ul></li></ul>" + - "If nothing is set, json is used.<br/>" + - "Since 6.1, XML format is deprecated, only JSON format should be used.") - .setPossibleValues("json", "xml") - .setDeprecatedSince("6.1"); - } - - public static WebService.NewParam addJsonOnlyFormatParam(WebService.NewAction action) { - return action.createParam(PARAM_FORMAT) - .setDescription("Only json response format is available") - .setPossibleValues("json"); - } - } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java index f6ca6e6c0c1..89ce77b8e5e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java @@ -24,6 +24,7 @@ public class ProjectsWsParameters { public static final String CONTROLLER = "api/projects"; public static final String ACTION_CREATE = "create"; + public static final String ACTION_INDEX = "index"; public static final String PARAM_PROJECT = "project"; public static final String PARAM_NAME = "name"; |