public class ComponentDao implements Dao {
private static List<ComponentDto> selectByQueryImpl(DbSession session, @Nullable String organizationUuid, ComponentQuery query, int offset, int limit) {
- Set<Long> componentIds = query.getComponentIds();
- if (componentIds != null && componentIds.isEmpty()) {
+ if (query.hasEmptySetOfComponents()) {
return emptyList();
}
return mapper(session).selectByQuery(organizationUuid, query, new RowBounds(offset, limit));
}
private static int countByQueryImpl(DbSession session, @Nullable String organizationUuid, ComponentQuery query) {
- Set<Long> componentIds = query.getComponentIds();
- if (componentIds != null && componentIds.isEmpty()) {
+ if (query.hasEmptySetOfComponents()) {
return 0;
}
+
return mapper(session).countByQuery(organizationUuid, query);
}
import java.util.Locale;
import java.util.Set;
+import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.db.WildcardPosition;
private final String language;
private final Boolean isPrivate;
private final Set<Long> componentIds;
+ private final Set<String> componentUuids;
+ private final Set<String> componentKeys;
private final Long analyzedBefore;
private final boolean onProvisionedOnly;
this.qualifiers = builder.qualifiers;
this.language = builder.language;
this.componentIds = builder.componentIds;
+ this.componentUuids = builder.componentUuids;
+ this.componentKeys = builder.componentKeys;
this.isPrivate = builder.isPrivate;
this.analyzedBefore = builder.analyzedBefore;
this.onProvisionedOnly = builder.onProvisionedOnly;
return componentIds;
}
+ @CheckForNull
+ public Set<String> getComponentUuids() {
+ return componentUuids;
+ }
+
+ @CheckForNull
+ public Set<String> getComponentKeys() {
+ return componentKeys;
+ }
+
@CheckForNull
public Boolean getPrivate() {
return isPrivate;
return onProvisionedOnly;
}
+ boolean hasEmptySetOfComponents() {
+ return Stream.of(componentIds, componentKeys, componentUuids)
+ .anyMatch(list -> list != null && list.isEmpty());
+ }
+
public static Builder builder() {
return new Builder();
}
private String language;
private Boolean isPrivate;
private Set<Long> componentIds;
+ private Set<String> componentUuids;
+ private Set<String> componentKeys;
private Long analyzedBefore;
private boolean onProvisionedOnly = false;
return this;
}
+ public Builder setComponentUuids(@Nullable Set<String> componentUuids) {
+ this.componentUuids = componentUuids;
+ return this;
+ }
+
+ public Builder setComponentKeys(@Nullable Set<String> componentKeys) {
+ this.componentKeys = componentKeys;
+ return this;
+ }
+
public Builder setPrivate(@Nullable Boolean isPrivate) {
this.isPrivate = isPrivate;
return this;
#{componentId,jdbcType=BIGINT}
</foreach>
</if>
+ <if test="query.componentKeys!=null">
+ and p.kee in
+ <foreach collection="query.componentKeys" item="componentKey" open="(" close=")" separator=",">
+ #{componentKey,jdbcType=BIGINT}
+ </foreach>
+ </if>
+ <if test="query.componentUuids!=null">
+ and p.uuid in
+ <foreach collection="query.componentUuids" item="componentUuid" open="(" close=")" separator=",">
+ #{componentUuid,jdbcType=BIGINT}
+ </foreach>
+ </if>
<if test="query.nameOrKeyQuery!=null">
and (
upper(p.name) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
@Test
public void selectByQuery_on_empty_list_of_component_id() {
+ db.components().insertPrivateProject();
ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT).setComponentIds(emptySet()).build();
+
List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, 0, 10);
int count = underTest.countByQuery(dbSession, dbQuery);
.doesNotContain(cLang.getId());
}
+ @Test
+ public void selectByQuery_on_empty_list_of_component_key() {
+ db.components().insertPrivateProject();
+ ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT).setComponentKeys(emptySet()).build();
+
+ List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, 0, 10);
+ int count = underTest.countByQuery(dbSession, dbQuery);
+
+ assertThat(result).isEmpty();
+ assertThat(count).isEqualTo(0);
+ }
+
+ @Test
+ public void selectByQuery_on_component_keys() {
+ OrganizationDto organizationDto = db.organizations().insert();
+ ComponentDto sonarqube = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto jdk8 = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto cLang = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentQuery query = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT)
+ .setComponentKeys(newHashSet(sonarqube.getDbKey(), jdk8.getDbKey())).build();
+
+ List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+
+ assertThat(result).hasSize(2).extracting(ComponentDto::getDbKey)
+ .containsExactlyInAnyOrder(sonarqube.getDbKey(), jdk8.getDbKey())
+ .doesNotContain(cLang.getDbKey());
+ }
+
+ @Test
+ public void selectByQuery_on_empty_list_of_component_uuids() {
+ db.components().insertPrivateProject();
+ ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT).setComponentUuids(emptySet()).build();
+
+ List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, 0, 10);
+ int count = underTest.countByQuery(dbSession, dbQuery);
+
+ assertThat(result).isEmpty();
+ assertThat(count).isEqualTo(0);
+ }
+
+ @Test
+ public void selectByQuery_on_component_uuids() {
+ OrganizationDto organizationDto = db.organizations().insert();
+ ComponentDto sonarqube = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto jdk8 = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentDto cLang = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+ ComponentQuery query = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT)
+ .setComponentUuids(newHashSet(sonarqube.uuid(), jdk8.uuid())).build();
+
+ List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+
+ assertThat(result).hasSize(2).extracting(ComponentDto::uuid)
+ .containsOnlyOnce(sonarqube.uuid(), jdk8.uuid())
+ .doesNotContain(cLang.uuid());
+ }
+
@Test
public void selectAncestors() {
// organization
*/
package org.sonar.db.component;
+import java.util.function.Supplier;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.resources.Qualifiers.PROJECT;
assertThat(underTest.getAnalyzedBefore()).isEqualTo(1_000_000_000L);
assertThat(underTest.isOnProvisionedOnly()).isFalse();
assertThat(underTest.isPartialMatchOnKey()).isFalse();
+ assertThat(underTest.hasEmptySetOfComponents()).isFalse();
}
@Test
assertThat(underTest.getNameOrKeyUpperLikeQuery()).isEqualTo("%NAME//KEY%");
}
+ @Test
+ public void empty_list_of_components() {
+ Supplier<ComponentQuery.Builder> query = () -> ComponentQuery.builder().setQualifiers(PROJECT);
+
+ assertThat(query.get().setComponentIds(emptySet()).build().hasEmptySetOfComponents()).isTrue();
+ assertThat(query.get().setComponentKeys(emptySet()).build().hasEmptySetOfComponents()).isTrue();
+ assertThat(query.get().setComponentUuids(emptySet()).build().hasEmptySetOfComponents()).isTrue();
+ assertThat(query.get().setComponentIds(singleton(404L)).build().hasEmptySetOfComponents()).isFalse();
+ assertThat(query.get().setComponentKeys(singleton("P1")).build().hasEmptySetOfComponents()).isFalse();
+ assertThat(query.get().setComponentUuids(singleton("U1")).build().hasEmptySetOfComponents()).isFalse();
+ }
+
@Test
public void fail_if_no_qualifier_provided() {
expectedException.expect(IllegalArgumentException.class);
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECTS;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_IDS;
public class BulkDeleteAction implements ProjectsWsAction {
private static final String ACTION = "bulk_delete";
- private static final String PARAM_PROJECT_IDS = "projectIds";
- private static final String PARAM_PROJECTS = "projects";
private final ComponentCleanerService componentCleanerService;
private final DbClient dbClient;
*/
package org.sonar.server.project.ws;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
import static org.sonar.server.project.Visibility.PRIVATE;
import static org.sonar.server.project.Visibility.PUBLIC;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.WsProjects.SearchWsResponse.Component;
import static org.sonarqube.ws.WsProjects.SearchWsResponse.newBuilder;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ON_PROVISIONED_ONLY;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECTS;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_IDS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
.setBooleanPossibleValues()
.setDefaultValue("false")
.setSince("6.6");
+
+ action
+ .createParam(PARAM_PROJECTS)
+ .setDescription("Comma-separated list of project keys")
+ .setSince("6.6")
+ .setExampleValue(String.join(",", KEY_PROJECT_EXAMPLE_001, KEY_PROJECT_EXAMPLE_002));
+
+ action
+ .createParam(PARAM_PROJECT_IDS)
+ .setDescription("Comma-separated list of project ids")
+ .setSince("6.6")
+ // parameter added to match api/projects/bulk_delete parameters
+ .setDeprecatedSince("6.6")
+ .setExampleValue(String.join(",", UUID_EXAMPLE_01, UUID_EXAMPLE_02));
}
@Override
.setVisibility(request.param(PARAM_VISIBILITY))
.setAnalyzedBefore(request.param(PARAM_ANALYZED_BEFORE))
.setOnProvisionedOnly(request.mandatoryParamAsBoolean(PARAM_ON_PROVISIONED_ONLY))
+ .setProjects(request.paramAsStrings(PARAM_PROJECTS))
+ .setProjectIds(request.paramAsStrings(PARAM_PROJECT_IDS))
.build();
}
OrganizationDto organization = support.getOrganization(dbSession, ofNullable(request.getOrganization()).orElseGet(defaultOrganizationProvider.get()::getKey));
userSession.checkPermission(OrganizationPermission.ADMINISTER, organization);
- ComponentQuery query = buildQuery(request);
+ ComponentQuery query = buildDbQuery(request);
Paging paging = buildPaging(dbSession, request, organization, query);
List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, paging.offset(), paging.pageSize());
Map<String, Long> analysisDateByComponentUuid = dbClient.snapshotDao()
}
}
- private static ComponentQuery buildQuery(SearchWsRequest request) {
+ private static ComponentQuery buildDbQuery(SearchWsRequest request) {
List<String> qualifiers = request.getQualifiers();
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(qualifiers.toArray(new String[qualifiers.size()]));
setNullable(request.getVisibility(), v -> query.setPrivate(Visibility.isPrivate(v)));
setNullable(request.getAnalyzedBefore(), d -> query.setAnalyzedBefore(parseDateOrDateTime(d).getTime()));
setNullable(request.isOnProvisionedOnly(), query::setOnProvisionedOnly);
+ setNullable(request.getProjects(), keys -> query.setComponentKeys(new HashSet<>(keys)));
+ setNullable(request.getProjectIds(), uuids -> query.setComponentUuids(new HashSet<>(uuids)));
return query.build();
}
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.assertj.core.api.Assertions;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ON_PROVISIONED_ONLY;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECTS;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_IDS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
public class SearchActionTest {
.doesNotContain(analyzedProject.getKey());
}
+ @Test
+ public void search_by_component_keys() {
+ userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
+ ComponentDto jdk = db.components().insertPrivateProject();
+ ComponentDto sonarqube = db.components().insertPrivateProject();
+ ComponentDto sonarlint = db.components().insertPrivateProject();
+
+ SearchWsResponse result = call(SearchWsRequest.builder()
+ .setProjects(Arrays.asList(jdk.getKey(), sonarqube.getKey()))
+ .build());
+
+ assertThat(result.getComponentsList()).extracting(Component::getKey)
+ .containsExactlyInAnyOrder(jdk.getKey(), sonarqube.getKey())
+ .doesNotContain(sonarlint.getKey());
+ }
+
+ @Test
+ public void search_by_component_uuids() {
+ userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
+ ComponentDto jdk = db.components().insertPrivateProject();
+ ComponentDto sonarqube = db.components().insertPrivateProject();
+ ComponentDto sonarlint = db.components().insertPrivateProject();
+
+ SearchWsResponse result = call(SearchWsRequest.builder()
+ .setProjectIds(Arrays.asList(jdk.uuid(), sonarqube.uuid()))
+ .build());
+
+ assertThat(result.getComponentsList()).extracting(Component::getKey)
+ .containsExactlyInAnyOrder(jdk.getKey(), sonarqube.getKey())
+ .doesNotContain(sonarlint.getKey());
+ }
+
@Test
public void fail_when_not_system_admin() throws Exception {
userSession.addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization());
assertThat(action.isInternal()).isFalse();
assertThat(action.since()).isEqualTo("6.3");
assertThat(action.handler()).isEqualTo(ws.getDef().handler());
- assertThat(action.params()).hasSize(8).extracting(Param::key)
- .containsExactlyInAnyOrder("organization", "q", "qualifiers", "p", "ps", "visibility", "analyzedBefore", "onProvisionedOnly");
+ assertThat(action.params()).extracting(Param::key)
+ .containsExactlyInAnyOrder("organization", "q", "qualifiers", "p", "ps", "visibility", "analyzedBefore", "onProvisionedOnly", "projects", "projectIds");
assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json"));
Param organization = action.param("organization");
setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(PAGE_SIZE, String.valueOf(pageSize)));
setNullable(wsRequest.getVisibility(), v -> request.setParam(PARAM_VISIBILITY, v));
setNullable(wsRequest.getAnalyzedBefore(), d -> request.setParam(PARAM_ANALYZED_BEFORE, d));
+ setNullable(wsRequest.getProjects(), l -> request.setParam(PARAM_PROJECTS, String.join(",", l)));
+ setNullable(wsRequest.getProjectIds(), l -> request.setParam(PARAM_PROJECT_IDS, String.join(",", l)));
request.setParam(PARAM_ON_PROVISIONED_ONLY, String.valueOf(wsRequest.isOnProvisionedOnly()));
return request.executeProtobuf(SearchWsResponse.class);
}
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ON_PROVISIONED_ONLY;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECTS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_ID;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_IDS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
.setParam(TEXT_QUERY, request.getQuery())
.setParam(PAGE, request.getPage())
.setParam(PAGE_SIZE, request.getPageSize())
- .setParam(PARAM_ON_PROVISIONED_ONLY, request.isOnProvisionedOnly());
+ .setParam(PARAM_ON_PROVISIONED_ONLY, request.isOnProvisionedOnly())
+ .setParam(PARAM_PROJECTS, request.getProjects())
+ .setParam(PARAM_PROJECT_IDS, request.getProjectIds());
return call(get, SearchWsResponse.parser());
}
public static final String PARAM_VISIBILITY = "visibility";
public static final String PARAM_ANALYZED_BEFORE = "analyzedBefore";
public static final String PARAM_ON_PROVISIONED_ONLY = "onProvisionedOnly";
+ public static final String PARAM_PROJECT_IDS = "projectIds";
+ public static final String PARAM_PROJECTS = "projects";
public static final String FILTER_LANGUAGES = "languages";
public static final String FILTER_TAGS = "tags";
private final Integer pageSize;
private final String analyzedBefore;
private final boolean onProvisionedOnly;
+ private final List<String> projects;
+ private final List<String> projectIds;
public SearchWsRequest(Builder builder) {
this.organization = builder.organization;
this.pageSize = builder.pageSize;
this.analyzedBefore = builder.analyzedBefore;
this.onProvisionedOnly = builder.onProvisionedOnly;
+ this.projects = builder.projects;
+ this.projectIds = builder.projectIds;
}
@CheckForNull
return onProvisionedOnly;
}
+ @CheckForNull
+ public List<String> getProjects() {
+ return projects;
+ }
+
+ @CheckForNull
+ public List<String> getProjectIds() {
+ return projectIds;
+ }
+
public static Builder builder() {
return new Builder();
}
private String visibility;
private String analyzedBefore;
private boolean onProvisionedOnly = false;
+ private List<String> projects;
+ private List<String> projectIds;
public Builder setOrganization(@Nullable String organization) {
this.organization = organization;
return this;
}
+ public Builder setProjects(@Nullable List<String> projects) {
+ this.projects = projects;
+ return this;
+ }
+
+ public Builder setProjectIds(@Nullable List<String> projectIds) {
+ this.projectIds = projectIds;
+ return this;
+ }
+
public SearchWsRequest build() {
+ checkArgument(projects==null || !projects.isEmpty(), "Project key list must not be empty");
+ checkArgument(projectIds==null || !projectIds.isEmpty(), "Project id list must not be empty");
checkArgument(pageSize == null || pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE);
return new SearchWsRequest(this);
}
import org.junit.rules.ExpectedException;
import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
public class SearchWsRequestTest {
.setPageSize(10000)
.build();
}
+
+ @Test
+ public void fail_if_project_key_list_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Project key list must not be empty");
+
+ SearchWsRequest.builder()
+ .setProjects(emptyList())
+ .build();
+ }
+
+ @Test
+ public void fail_if_project_id_list_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Project id list must not be empty");
+
+ SearchWsRequest.builder()
+ .setProjectIds(emptyList())
+ .build();
+ }
}
}
@Test
- public void search_on_key_partial_match_case_insensitive() {
+ public void search_on_key_query_partial_match_case_insensitive() {
Organizations.Organization organization = tester.organizations().generate();
CreateWsResponse.Project lowerCaseProject = tester.projects().generate(organization, p -> p.setKey("project-key"));
CreateWsResponse.Project upperCaseProject = tester.projects().generate(organization, p -> p.setKey("PROJECT-KEY"));