]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10356 Oracle error on some WS involving more than 1000 projects
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 13 Feb 2018 19:18:49 +0000 (20:18 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 22 Feb 2018 08:44:52 +0000 (09:44 +0100)
12 files changed:
server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java
server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchMyProjectsAction.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchMyProjectsActionTest.java

index 243436a84a39d1f0f91801c02a1a465082e9867c..9b850e40f655c2f24dd69519fb02cc2a1f8ccb8e 100644 (file)
@@ -47,6 +47,7 @@ import javax.annotation.Nullable;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
 
@@ -338,4 +339,14 @@ public class DatabaseUtils {
       }
     };
   }
+
+  /**
+   * @throws IllegalArgumentException if the collection is not null and has strictly more
+   * than {@link #PARTITION_SIZE_FOR_ORACLE} values.
+   */
+  public static void checkThatNotTooManyConditions(@Nullable Collection<?> values, String message) {
+    if (values != null) {
+      checkArgument(values.size() <= PARTITION_SIZE_FOR_ORACLE, message);
+    }
+  }
 }
index 98c6d1e46615a88662145cf75e7149aa54e2d1d9..b32852e37e6fe7456280f21d35acbdc52622633f 100644 (file)
@@ -332,4 +332,20 @@ public class DatabaseUtilsTest {
       assertThat(DatabaseUtils.tableExists("foo", connection)).isFalse();
     }
   }
+
+  @Test
+  public void checkThatNotTooManyConditions_does_not_fail_if_less_than_1000_conditions() {
+    DatabaseUtils.checkThatNotTooManyConditions(null, "unused");
+    DatabaseUtils.checkThatNotTooManyConditions(Collections.emptySet(), "unused");
+    DatabaseUtils.checkThatNotTooManyConditions(Collections.nCopies(10, "foo"), "unused");
+    DatabaseUtils.checkThatNotTooManyConditions(Collections.nCopies(1_000, "foo"), "unused");
+  }
+
+  @Test
+  public void checkThatNotTooManyConditions_throws_IAE_if_strictly_more_than_1000_conditions() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("the message");
+
+    DatabaseUtils.checkThatNotTooManyConditions(Collections.nCopies(1_001, "foo"), "the message");
+  }
 }
index 04a297326babbc34aed9b451b9c0ecdd483a0cee..24eb17266cef830587b57682760b7aabcbadb7cc 100644 (file)
@@ -45,6 +45,7 @@ import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.isBlank;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
+import static org.sonar.db.DatabaseUtils.checkThatNotTooManyConditions;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
 import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
@@ -56,6 +57,7 @@ public class ComponentDao implements Dao {
     if (query.hasEmptySetOfComponents()) {
       return emptyList();
     }
+    checkThatNotTooManyComponents(query);
     return mapper(session).selectByQuery(organizationUuid, query, new RowBounds(offset, limit));
   }
 
@@ -63,7 +65,7 @@ public class ComponentDao implements Dao {
     if (query.hasEmptySetOfComponents()) {
       return 0;
     }
-
+    checkThatNotTooManyComponents(query);
     return mapper(session).countByQuery(organizationUuid, query);
   }
 
@@ -103,19 +105,37 @@ public class ComponentDao implements Dao {
     return componentDto.get();
   }
 
+  /**
+   * Same as {@link #selectByQuery(DbSession, String, ComponentQuery, int, int)} except
+   * that the filter on organization is disabled.
+   */
   public List<ComponentDto> selectByQuery(DbSession session, ComponentQuery query, int offset, int limit) {
     return selectByQueryImpl(session, null, query, offset, limit);
   }
 
+  /**
+   * @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   */
   public List<ComponentDto> selectByQuery(DbSession dbSession, String organizationUuid, ComponentQuery query, int offset, int limit) {
     requireNonNull(organizationUuid, "organizationUuid can't be null");
     return selectByQueryImpl(dbSession, organizationUuid, query, offset, limit);
   }
 
+  /**
+   * Same as {@link #countByQuery(DbSession, String, ComponentQuery)} except
+   * that the filter on organization is disabled.
+   */
   public int countByQuery(DbSession session, ComponentQuery query) {
     return countByQueryImpl(session, null, query);
   }
 
+  /**
+   * @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   */
   public int countByQuery(DbSession session, String organizationUuid, ComponentQuery query) {
     requireNonNull(organizationUuid, "organizationUuid can't be null");
     return countByQueryImpl(session, organizationUuid, query);
@@ -346,4 +366,11 @@ public class ComponentDao implements Dao {
   public List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(DbSession dbSession, String mergeBranchUuid) {
     return mapper(dbSession).selectComponentKeysHavingIssuesToMerge(mergeBranchUuid);
   }
+
+  private static void checkThatNotTooManyComponents(ComponentQuery query) {
+    checkThatNotTooManyConditions(query.getComponentIds(), "Too many component ids in query");
+    checkThatNotTooManyConditions(query.getComponentKeys(), "Too many component keys in query");
+    checkThatNotTooManyConditions(query.getComponentUuids(), "Too many component UUIDs in query");
+  }
+
 }
index a47703e7b608dd00c58bd275d8a3e4b87b58ada9..9dda85ffaff73ae7dfad9a9c16248f6bc5c59ad1 100644 (file)
@@ -27,6 +27,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
 import javax.annotation.Nullable;
 import org.assertj.core.api.ListAssert;
 import org.junit.Rule;
@@ -720,7 +722,7 @@ public class ComponentDaoTest {
   }
 
   @Test
-  public void select_provisioned() {
+  public void selectByQuery_provisioned() {
     OrganizationDto organization = db.organizations().insert();
     ComponentDto provisionedProject = db.components()
       .insertComponent(newPrivateProjectDto(organization).setDbKey("provisioned.project").setName("Provisioned Project"));
@@ -777,6 +779,51 @@ public class ComponentDaoTest {
     assertThat(underTest.countByQuery(dbSession, organization.getUuid(), query.get().setQualifiers(Qualifiers.PROJECT, Qualifiers.VIEW).build())).isEqualTo(1);
   }
 
+  @Test
+  public void countByQuery_with_organization_throws_NPE_of_organizationUuid_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("organizationUuid can't be null");
+
+    underTest.countByQuery(dbSession, null, ALL_PROJECTS_COMPONENT_QUERY);
+  }
+
+  @Test
+  public void countByQuery_throws_IAE_if_too_many_component_ids() {
+    Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentIds(ids);
+
+    assertThatCountByQueryThrowsIAE(query, "Too many component ids in query");
+  }
+
+  @Test
+  public void countByQuery_throws_IAE_if_too_many_component_keys() {
+    Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentKeys(keys);
+
+    assertThatCountByQueryThrowsIAE(query, "Too many component keys in query");
+  }
+
+  @Test
+  public void countByQuery_throws_IAE_if_too_many_component_uuids() {
+    Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentUuids(uuids);
+
+    assertThatCountByQueryThrowsIAE(query, "Too many component UUIDs in query");
+  }
+
+  private void assertThatCountByQueryThrowsIAE(ComponentQuery.Builder query, String expectedMessage) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(expectedMessage);
+
+    underTest.countByQuery(dbSession, query.build());
+  }
+
   @Test
   public void select_ghost_projects() {
     OrganizationDto organization = db.organizations().insert();
@@ -1007,11 +1054,40 @@ public class ComponentDaoTest {
   }
 
   @Test
-  public void countByQuery_with_organization_throws_NPE_of_organizationUuid_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("organizationUuid can't be null");
+  public void selectByQuery_throws_IAE_if_too_many_component_ids() {
+    Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentIds(ids);
 
-    underTest.countByQuery(dbSession, null, ALL_PROJECTS_COMPONENT_QUERY);
+    assertThatSelectByQueryThrowsIAE(query, "Too many component ids in query");
+  }
+
+  @Test
+  public void selectByQuery_throws_IAE_if_too_many_component_keys() {
+    Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentKeys(keys);
+
+    assertThatSelectByQueryThrowsIAE(query, "Too many component keys in query");
+  }
+
+  @Test
+  public void selectByQuery_throws_IAE_if_too_many_component_uuids() {
+    Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+    ComponentQuery.Builder query = ComponentQuery.builder()
+      .setQualifiers(Qualifiers.PROJECT)
+      .setComponentUuids(uuids);
+
+    assertThatSelectByQueryThrowsIAE(query, "Too many component UUIDs in query");
+  }
+
+  private void assertThatSelectByQueryThrowsIAE(ComponentQuery.Builder query, String expectedMessage) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(expectedMessage);
+
+    underTest.selectByQuery(dbSession, query.build(), 0, Integer.MAX_VALUE);
   }
 
   @Test
index df2dada5a2a5e2ac3ebb1549ed01deaa659714b6..a86482a8a9aa7892ccac14fbd26b801857b18c1e 100644 (file)
@@ -25,10 +25,12 @@ import java.util.List;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
@@ -45,6 +47,7 @@ import javax.annotation.Nullable;
 
 import static java.util.Collections.singleton;
 import static java.util.Objects.requireNonNull;
+import static java.lang.String.format;
 import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
 import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdmin;
@@ -91,6 +94,7 @@ public class BulkApplyTemplateAction implements PermissionsWsAction {
         "Requires the following permission: 'Administer System'.")
       .setPost(true)
       .setSince("5.5")
+      .setChangelog(new Change("6.7.2", format("Parameter %s accepts maximum %d values", PARAM_PROJECTS, DatabaseUtils.PARTITION_SIZE_FOR_ORACLE)))
       .setHandler(this);
 
     action.createParam(Param.TEXT_QUERY)
@@ -110,6 +114,9 @@ public class BulkApplyTemplateAction implements PermissionsWsAction {
       .createParam(PARAM_PROJECTS)
       .setDescription("Comma-separated list of project keys")
       .setSince("6.6")
+      // Limitation of ComponentDao#selectByQuery(), max 1000 values are accepted.
+      // Restricting size of HTTP parameter allows to not fail with SQL error
+      .setMaxValuesAllowed(DatabaseUtils.PARTITION_SIZE_FOR_ORACLE)
       .setExampleValue(String.join(",", KEY_PROJECT_EXAMPLE_001, KEY_PROJECT_EXAMPLE_002));
 
     action.createParam(PARAM_VISIBILITY)
index 95b23b2c66366d33dcd6fdbb7a9df2c3cd7d29cf..dbd400435d76c241cdb5996a4107451d2b019136 100644 (file)
  */
 package org.sonar.server.project.ws;
 
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -32,6 +36,7 @@ import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.project.Visibility;
 import org.sonar.server.user.UserSession;
 
+import static java.lang.Math.min;
 import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.VIEW;
@@ -73,7 +78,8 @@ public class BulkDeleteAction implements ProjectsWsAction {
       .setDescription("Delete one or several projects.<br />" +
         "Requires 'Administer System' permission.")
       .setSince("5.2")
-      .setHandler(this);
+      .setHandler(this)
+      .setChangelog(new Change("6.7.2", "Only the 1'000 first items in project filters are taken into account"));
 
     support.addOrganizationParam(action);
 
@@ -85,7 +91,7 @@ public class BulkDeleteAction implements ProjectsWsAction {
 
     action
       .createParam(PARAM_PROJECT_IDS)
-      .setDescription("Comma-separated list of project ids")
+      .setDescription("Comma-separated list of project ids. Only the 1'000 first ids are used. Others are silently ignored.")
       .setDeprecatedKey("ids", "6.4")
       .setDeprecatedSince("6.4")
       .setExampleValue(String.join(",", UUID_EXAMPLE_01, UUID_EXAMPLE_02));
@@ -147,8 +153,16 @@ public class BulkDeleteAction implements ProjectsWsAction {
       .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))
+      .setProjects(restrictTo1000Values(request.paramAsStrings(PARAM_PROJECTS)))
+      .setProjectIds(restrictTo1000Values(request.paramAsStrings(PARAM_PROJECT_IDS)))
       .build();
   }
+
+  @CheckForNull
+  private static List<String> restrictTo1000Values(@Nullable List<String> values) {
+    if (values == null) {
+      return null;
+    }
+    return values.subList(0, min(values.size(), 1_000));
+  }
 }
index e4b97ba9eb088998a5b093796e6c32bf16e599db..1e4ba6fe8ba3a37e77193bb4bb4db6c1585c15ea 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.Paging;
 import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
@@ -42,6 +43,7 @@ import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Projects.SearchWsResponse;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
 import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.VIEW;
@@ -89,7 +91,9 @@ public class SearchAction implements ProjectsWsAction {
       .setResponseExample(getClass().getResource("search-example.json"))
       .setHandler(this);
 
-    action.setChangelog(new Change("6.4", "The 'uuid' field is deprecated in the response"));
+    action.setChangelog(
+      new Change("6.4", "The 'uuid' field is deprecated in the response"),
+      new Change("6.7.2", format("Parameters %s and %s accept maximum %d values", PARAM_PROJECTS, PARAM_PROJECT_IDS, DatabaseUtils.PARTITION_SIZE_FOR_ORACLE)));
 
     action.createParam(Param.TEXT_QUERY)
       .setDescription("Limit search to: <ul>" +
@@ -129,6 +133,9 @@ public class SearchAction implements ProjectsWsAction {
       .createParam(PARAM_PROJECTS)
       .setDescription("Comma-separated list of project keys")
       .setSince("6.6")
+      // Limitation of ComponentDao#selectByQuery(), max 1000 values are accepted.
+      // Restricting size of HTTP parameter allows to not fail with SQL error
+      .setMaxValuesAllowed(DatabaseUtils.PARTITION_SIZE_FOR_ORACLE)
       .setExampleValue(String.join(",", KEY_PROJECT_EXAMPLE_001, KEY_PROJECT_EXAMPLE_002));
 
     action
@@ -137,6 +144,9 @@ public class SearchAction implements ProjectsWsAction {
       .setSince("6.6")
       // parameter added to match api/projects/bulk_delete parameters
       .setDeprecatedSince("6.6")
+      // Limitation of ComponentDao#selectByQuery(), max 1000 values are accepted.
+      // Restricting size of HTTP parameter allows to not fail with SQL error
+      .setMaxValuesAllowed(DatabaseUtils.PARTITION_SIZE_FOR_ORACLE)
       .setExampleValue(String.join(",", UUID_EXAMPLE_01, UUID_EXAMPLE_02));
   }
 
index 1ec0e29c2a1d01943be66fb2611f16f08733b5b3..9752e19127fdce808b4275e281960383564afc73 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.web.UserRole;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
@@ -69,7 +70,7 @@ public class SearchMyProjectsAction implements ProjectsWsAction {
   @Override
   public void define(WebService.NewController context) {
     WebService.NewAction action = context.createAction("search_my_projects")
-      .setDescription("Return list of projects for which the current user has 'Administer' permission.")
+      .setDescription("Return list of projects for which the current user has 'Administer' permission. Maximum 1'000 projects are returned.")
       .setResponseExample(getClass().getResource("search_my_projects-example.json"))
       .addPagingParams(100, MAX_SIZE)
       .setSince("6.0")
@@ -193,7 +194,7 @@ public class SearchMyProjectsAction implements ProjectsWsAction {
     List<Long> componentIds = dbClient.roleDao().selectComponentIdsByPermissionAndUserId(dbSession, UserRole.ADMIN, userId);
     ComponentQuery dbQuery = ComponentQuery.builder()
             .setQualifiers(Qualifiers.PROJECT)
-            .setComponentIds(ImmutableSet.copyOf(componentIds))
+            .setComponentIds(ImmutableSet.copyOf(componentIds.subList(0, Math.min(componentIds.size(), DatabaseUtils.PARTITION_SIZE_FOR_ORACLE))))
             .build();
 
     return new ProjectsResult(
index 978e3732f40a28ff121692f2ab139cc6bbefe42b..e0b25a0d667cc7062ee30034e189f60f77db98e8 100644 (file)
@@ -19,7 +19,9 @@
  */
 package org.sonar.server.permission.ws.template;
 
+import java.util.Collections;
 import java.util.List;
+import org.apache.commons.lang.StringUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.resources.Qualifiers;
@@ -102,7 +104,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void bulk_apply_template_by_template_uuid() throws Exception {
+  public void bulk_apply_template_by_template_uuid() {
     // this project should not be applied the template
     OrganizationDto otherOrganization = db.organizations().insert();
     db.components().insertPrivateProject(otherOrganization);
@@ -135,7 +137,19 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void bulk_apply_template_by_template_name() throws Exception {
+  public void request_throws_IAE_if_more_than_1000_projects() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("'projects' can contains only 1000 values, got 1001");
+
+    newRequest()
+      .setParam(PARAM_ORGANIZATION, organization.getKey())
+      .setParam(PARAM_TEMPLATE_NAME, template1.getName())
+      .setParam(PARAM_PROJECTS, StringUtils.join(Collections.nCopies(1_001, "foo"), ","))
+      .execute();
+  }
+
+  @Test
+  public void bulk_apply_template_by_template_name() {
     ComponentDto privateProject = db.components().insertPrivateProject(organization);
     ComponentDto publicProject = db.components().insertPublicProject(organization);
     loginAsAdmin(organization);
@@ -150,7 +164,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_qualifiers() throws Exception {
+  public void apply_template_by_qualifiers() {
     ComponentDto publicProject = db.components().insertPublicProject(organization);
     ComponentDto privateProject = db.components().insertPrivateProject(organization);
     ComponentDto view = db.components().insertComponent(newView(organization));
@@ -169,7 +183,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_query_on_name_and_key_public_project() throws Exception {
+  public void apply_template_by_query_on_name_and_key_public_project() {
     ComponentDto publicProjectFoundByKey = ComponentTesting.newPublicProjectDto(organization).setDbKey("sonar");
     db.components().insertProjectAndSnapshot(publicProjectFoundByKey);
     ComponentDto publicProjectFoundByName = ComponentTesting.newPublicProjectDto(organization).setName("name-sonar-name");
@@ -189,7 +203,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_query_on_name_and_key() throws Exception {
+  public void apply_template_by_query_on_name_and_key() {
     // partial match on key
     ComponentDto privateProjectFoundByKey = ComponentTesting.newPrivateProjectDto(organization).setDbKey("sonarqube");
     db.components().insertProjectAndSnapshot(privateProjectFoundByKey);
@@ -210,7 +224,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_project_keys() throws Exception {
+  public void apply_template_by_project_keys() {
     ComponentDto project1 = db.components().insertPrivateProject(organization);
     ComponentDto project2 = db.components().insertPrivateProject(organization);
     ComponentDto untouchedProject = db.components().insertPrivateProject(organization);
@@ -227,7 +241,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_provisioned_only() throws Exception {
+  public void apply_template_by_provisioned_only() {
     ComponentDto provisionedProject1 = db.components().insertPrivateProject(organization);
     ComponentDto provisionedProject2 = db.components().insertPrivateProject(organization);
     ComponentDto analyzedProject = db.components().insertPrivateProject(organization);
@@ -245,7 +259,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_analyzed_before() throws Exception {
+  public void apply_template_by_analyzed_before() {
     ComponentDto oldProject1 = db.components().insertPrivateProject(organization);
     ComponentDto oldProject2 = db.components().insertPrivateProject(organization);
     ComponentDto recentProject = db.components().insertPrivateProject(organization);
@@ -265,7 +279,7 @@ public class BulkApplyTemplateActionTest extends BasePermissionWsTest<BulkApplyT
   }
 
   @Test
-  public void apply_template_by_visibility() throws Exception {
+  public void apply_template_by_visibility() {
     ComponentDto privateProject1 = db.components().insertPrivateProject(organization);
     ComponentDto privateProject2 = db.components().insertPrivateProject(organization);
     ComponentDto publicProject = db.components().insertPublicProject(organization);
index 1709788a7c9b7de9041d8828d50b2dcc7a9dc8eb..617e0f07290941616f40feb15efbc8bcc7fc4ab1 100644 (file)
  */
 package org.sonar.server.project.ws;
 
-import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.util.Date;
+import java.util.List;
 import java.util.stream.IntStream;
+import org.apache.commons.lang.StringUtils;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -31,6 +32,7 @@ import org.mockito.ArgumentCaptor;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
@@ -209,6 +211,23 @@ public class BulkDeleteActionTest {
     verifyNoDeletions();
   }
 
+  /**
+   * SONAR-10356
+   */
+  @Test
+  public void delete_only_the_1000_first_projects() {
+    userSession.logIn().addPermission(ADMINISTER, org1);
+    List<String> keys = IntStream.range(0, 1_010).mapToObj(i -> "key" + i).collect(MoreCollectors.toArrayList());
+    keys.forEach(key -> db.components().insertPrivateProject(org1, p -> p.setDbKey(key)));
+
+    ws.newRequest()
+      .setParam("organization", org1.getKey())
+      .setParam("projects", StringUtils.join(keys, ","))
+      .execute();
+
+    verify(componentCleanerService, times(1_000)).delete(any(DbSession.class), any(ComponentDto.class));
+  }
+
   @Test
   public void organization_administrator_deletes_projects_by_keys_in_his_organization() {
     userSession.logIn().addPermission(ADMINISTER, org1);
index e071564fe3f0b774e7ed77110c01bb54289d5de8..04d6aaf3fd71d2c1af867f6a39a4ee1238cea132 100644 (file)
 package org.sonar.server.project.ws;
 
 import com.google.common.base.Joiner;
-import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import org.assertj.core.api.Assertions;
@@ -303,6 +302,26 @@ public class SearchActionTest {
       .doesNotContain(sonarlint.getKey());
   }
 
+  @Test
+  public void request_throws_IAE_if_more_than_1000_projects() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("'projects' can contains only 1000 values, got 1001");
+
+    call(SearchRequest.builder()
+      .setProjects(Collections.nCopies(1_001, "foo"))
+      .build());
+  }
+
+  @Test
+  public void request_throws_IAE_if_more_than_1000_project_ids() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("'projectIds' can contains only 1000 values, got 1001");
+
+    call(SearchRequest.builder()
+      .setProjectIds(Collections.nCopies(1_001, "foo"))
+      .build());
+  }
+
   @Test
   public void fail_when_not_system_admin() {
     userSession.addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization());
index bcb8a89785b3c909fa211c627334ff8aade038c8..28f39588fa5d983d3fbe91eeeac029b3876b82a1 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.project.ws;
 
+import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -114,12 +115,25 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, jdk7);
     db.users().insertProjectPermissionOnUser(anotherUser, UserRole.ADMIN, cLang);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(1);
     assertThat(result.getProjects(0).getId()).isEqualTo(jdk7.uuid());
   }
 
+  @Test
+  public void return_only_first_1000_projects() {
+    OrganizationDto organization = db.organizations().insert();
+    IntStream.range(0, 1_010).forEach(i -> {
+      ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+      db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, project);
+    });
+
+    SearchMyProjectsWsResponse result = callWs();
+
+    assertThat(result.getPaging().getTotal()).isEqualTo(1_000);
+  }
+
   @Test
   public void sort_projects_by_name() {
     OrganizationDto organizationDto = db.organizations().insert();
@@ -131,7 +145,7 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, a_project);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, c_project);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(3);
     assertThat(result.getProjectsList()).extracting(Project::getId)
@@ -164,7 +178,7 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, jdk7);
     db.users().insertProjectPermissionOnUser(user, UserRole.ISSUE_ADMIN, clang);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(1);
     assertThat(result.getProjects(0).getId()).isEqualTo(jdk7.uuid());
@@ -179,7 +193,7 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, jdk7);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, view);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(1);
     assertThat(result.getProjects(0).getId()).isEqualTo(jdk7.uuid());
@@ -191,7 +205,7 @@ public class SearchMyProjectsActionTest {
     ComponentDto branch = db.components().insertProjectBranch(project);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, project);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsList())
       .extracting(Project::getKey)
@@ -210,7 +224,7 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnGroup(group, UserRole.ADMIN, jdk7);
     db.users().insertProjectPermissionOnGroup(group, UserRole.USER, cLang);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(1);
     assertThat(result.getProjects(0).getId()).isEqualTo(jdk7.uuid());
@@ -232,7 +246,7 @@ public class SearchMyProjectsActionTest {
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, sonarqube);
     db.users().insertProjectPermissionOnGroup(group, UserRole.ADMIN, sonarqube);
 
-    SearchMyProjectsWsResponse result = call_ws();
+    SearchMyProjectsWsResponse result = callWs();
 
     assertThat(result.getProjectsCount()).isEqualTo(3);
     assertThat(result.getProjectsList()).extracting(Project::getId).containsOnly(jdk7.uuid(), cLang.uuid(), sonarqube.uuid());
@@ -249,7 +263,7 @@ public class SearchMyProjectsActionTest {
     userSession.anonymous();
     expectedException.expect(UnauthorizedException.class);
 
-    call_ws();
+    callWs();
   }
 
   private ComponentDto insertClang(OrganizationDto organizationDto) {
@@ -271,7 +285,7 @@ public class SearchMyProjectsActionTest {
       .setDbKey("Java"));
   }
 
-  private SearchMyProjectsWsResponse call_ws() {
+  private SearchMyProjectsWsResponse callWs() {
     return ws.newRequest()
       .executeProtobuf(SearchMyProjectsWsResponse.class);
   }