@@ -33,6 +33,8 @@ import org.sonar.server.es.SearchResult; | |||
import org.sonar.server.search.QueryContext; | |||
import org.sonar.server.user.UserSession; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchAction implements RequestHandler { | |||
public static final String PARAM_TYPE = "type"; | |||
@@ -61,7 +63,7 @@ public class SearchAction implements RequestHandler { | |||
.setDescription("Activity type") | |||
.setPossibleValues(Activity.Type.values()); | |||
action.addPagingParams(10); | |||
action.addPagingParams(10, MAX_LIMIT); | |||
action.addFieldsParam(docToJsonMapping.supportedFields()); | |||
} | |||
@@ -43,6 +43,7 @@ import org.sonar.server.user.UserSession; | |||
import static com.google.common.collect.Sets.newLinkedHashSet; | |||
import static org.sonar.api.server.ws.WebService.Param.PAGE; | |||
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchViewComponentsAction implements RequestHandler { | |||
@@ -80,7 +81,7 @@ public class SearchViewComponentsAction implements RequestHandler { | |||
.setDescription("UTF-8 search query") | |||
.setExampleValue("sonar"); | |||
action.addPagingParams(10); | |||
action.addPagingParams(10, MAX_LIMIT); | |||
} | |||
@Override |
@@ -19,19 +19,18 @@ | |||
*/ | |||
package org.sonar.server.es; | |||
import com.google.common.base.Preconditions; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import javax.annotation.Nullable; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.LinkedHashSet; | |||
import java.util.Set; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
/** | |||
* Various Elasticsearch request options: paging, fields and facets | |||
@@ -41,6 +40,7 @@ public class SearchOptions { | |||
public static final int DEFAULT_OFFSET = 0; | |||
public static final int DEFAULT_LIMIT = 10; | |||
public static final int MAX_LIMIT = 500; | |||
private static final String MSG_MAX_LIMIT_ERROR = "Page size must be less than " + MAX_LIMIT; | |||
private int offset = DEFAULT_OFFSET; | |||
private int limit = DEFAULT_LIMIT; | |||
@@ -58,7 +58,7 @@ public class SearchOptions { | |||
* Sets the offset of the first result to return (zero-based). | |||
*/ | |||
public SearchOptions setOffset(int offset) { | |||
Preconditions.checkArgument(offset >= 0, "Offset must be positive"); | |||
checkArgument(offset >= 0, "Offset must be positive"); | |||
this.offset = offset; | |||
return this; | |||
} | |||
@@ -68,7 +68,7 @@ public class SearchOptions { | |||
* {@link #MAX_LIMIT} is used. | |||
*/ | |||
public SearchOptions setPage(int page, int pageSize) { | |||
Preconditions.checkArgument(page >= 1, "Page must be greater or equal to 1 (got " + page + ")"); | |||
checkArgument(page >= 1, "Page must be greater or equal to 1 (got " + page + ")"); | |||
setLimit(pageSize); | |||
setOffset((page * this.limit) - this.limit); | |||
return this; | |||
@@ -89,10 +89,11 @@ public class SearchOptions { | |||
* Sets the limit on the number of results to return. | |||
*/ | |||
public SearchOptions setLimit(int limit) { | |||
checkArgument(limit <= MAX_LIMIT, MSG_MAX_LIMIT_ERROR); | |||
if (limit <= 0) { | |||
this.limit = MAX_LIMIT; | |||
} else { | |||
this.limit = Math.min(limit, MAX_LIMIT); | |||
this.limit = limit; | |||
} | |||
return this; | |||
} |
@@ -54,6 +54,7 @@ import static com.google.common.collect.FluentIterable.from; | |||
import static com.google.common.collect.Iterables.concat; | |||
import static java.util.Collections.singletonList; | |||
import static org.sonar.api.utils.Paging.forPageIndex; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.issue.IssueFilterParameters.ACTION_PLANS; | |||
import static org.sonarqube.ws.client.issue.IssueFilterParameters.ADDITIONAL_FIELDS; | |||
@@ -119,7 +120,7 @@ public class SearchAction implements IssuesWsAction { | |||
.setSince("3.6") | |||
.setResponseExample(Resources.getResource(this.getClass(), "example-search.json")); | |||
action.addPagingParams(100); | |||
action.addPagingParams(100, MAX_LIMIT); | |||
action.createParam(Param.FACETS) | |||
.setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") | |||
.setPossibleValues(IssueIndex.SUPPORTED_FACETS); |
@@ -47,6 +47,7 @@ import org.sonar.server.user.UserSession; | |||
import org.sonar.server.user.index.UserIndex; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
import static org.sonar.server.measure.custom.ws.CustomMeasureValidator.checkPermissions; | |||
public class SearchAction implements CustomMeasuresWsAction { | |||
@@ -76,7 +77,7 @@ public class SearchAction implements CustomMeasuresWsAction { | |||
"Requires 'Administer System' permission or 'Administer' permission on the project.") | |||
.setSince("5.2") | |||
.addFieldsParam(CustomMeasureJsonWriter.OPTIONAL_FIELDS) | |||
.addPagingParams(100) | |||
.addPagingParams(100, MAX_LIMIT) | |||
.setResponseExample(getClass().getResource("example-search.json")) | |||
.setHandler(this); | |||
@@ -69,7 +69,7 @@ public class SearchAction implements MetricsWsAction { | |||
.setSince("5.2") | |||
.setDescription("Search for metrics") | |||
.setResponseExample(getClass().getResource("example-search.json")) | |||
.addPagingParams(100) | |||
.addPagingParams(100,500) | |||
.addFieldsParam(MetricJsonWriter.OPTIONAL_FIELDS) | |||
.setHandler(this); | |||
@@ -39,6 +39,7 @@ import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.user.UserSession; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class GhostsAction implements ProjectsWsAction { | |||
public static final String ACTION = "ghosts"; | |||
@@ -59,7 +60,7 @@ public class GhostsAction implements ProjectsWsAction { | |||
.setDescription("List ghost projects.<br /> Requires 'Administer System' permission.") | |||
.setResponseExample(Resources.getResource(getClass(), "projects-example-ghosts.json")) | |||
.setSince("5.2") | |||
.addPagingParams(100) | |||
.addPagingParams(100, MAX_LIMIT) | |||
.addFieldsParam(POSSIBLE_FIELDS) | |||
.addSearchQuery("sonar", "names", "keys") | |||
.setHandler(this); |
@@ -38,6 +38,7 @@ import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.user.UserSession; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class ProvisionedAction implements ProjectsWsAction { | |||
private static final Set<String> POSSIBLE_FIELDS = newHashSet("uuid", "key", "name", "creationDate"); | |||
@@ -60,7 +61,7 @@ public class ProvisionedAction implements ProjectsWsAction { | |||
.setSince("5.2") | |||
.setResponseExample(Resources.getResource(getClass(), "projects-example-provisioned.json")) | |||
.setHandler(this) | |||
.addPagingParams(100) | |||
.addPagingParams(100, MAX_LIMIT) | |||
.addSearchQuery("sonar", "names", "keys") | |||
.addFieldsParam(POSSIBLE_FIELDS); | |||
} |
@@ -45,6 +45,7 @@ import org.sonar.server.qualityprofile.QProfileFactory; | |||
import org.sonar.server.search.Result; | |||
import static org.sonar.api.utils.Paging.forPageIndex; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class ChangelogAction implements QProfileWsAction { | |||
@@ -74,7 +75,7 @@ public class ChangelogAction implements QProfileWsAction { | |||
QProfileIdentificationParamUtils.defineProfileParams(changelog, languages); | |||
changelog.addPagingParams(50); | |||
changelog.addPagingParams(50, MAX_LIMIT); | |||
changelog.createParam(PARAM_SINCE) | |||
.setDescription("Start date for the changelog.") |
@@ -50,6 +50,8 @@ import org.sonar.server.ws.WsUtils; | |||
import org.sonarqube.ws.Common; | |||
import org.sonarqube.ws.WsTests; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class ListAction implements TestsWsAction { | |||
public static final String TEST_ID = "testId"; | |||
public static final String TEST_FILE_ID = "testFileId"; | |||
@@ -85,7 +87,7 @@ public class ListAction implements TestsWsAction { | |||
.setSince("5.2") | |||
.setResponseExample(Resources.getResource(getClass(), "tests-example-list.json")) | |||
.setHandler(this) | |||
.addPagingParams(100); | |||
.addPagingParams(100, MAX_LIMIT); | |||
action | |||
.createParam(TEST_FILE_ID) |
@@ -41,6 +41,8 @@ import org.sonar.server.es.SearchResult; | |||
import org.sonar.server.user.index.UserDoc; | |||
import org.sonar.server.user.index.UserIndex; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchAction implements UsersWsAction { | |||
private final UserIndex userIndex; | |||
@@ -62,7 +64,7 @@ public class SearchAction implements UsersWsAction { | |||
.setResponseExample(getClass().getResource("example-search.json")); | |||
action.addFieldsParam(UserJsonWriter.FIELDS); | |||
action.addPagingParams(50); | |||
action.addPagingParams(50, MAX_LIMIT); | |||
action.createParam(Param.TEXT_QUERY) | |||
.setDescription("Filter on login or name."); |
@@ -40,6 +40,8 @@ import org.sonar.db.MyBatis; | |||
import org.sonar.db.user.GroupDto; | |||
import org.sonar.server.es.SearchOptions; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchAction implements UserGroupsWsAction { | |||
private static final String FIELD_ID = "id"; | |||
@@ -62,7 +64,7 @@ public class SearchAction implements UserGroupsWsAction { | |||
.setResponseExample(getClass().getResource("example-search.json")) | |||
.setSince("5.2") | |||
.addFieldsParam(ALL_FIELDS) | |||
.addPagingParams(100) | |||
.addPagingParams(100, MAX_LIMIT) | |||
.addSearchQuery("sonar-users", "names"); | |||
} | |||
@@ -36,6 +36,7 @@ import org.sonar.server.ws.WsTester; | |||
import org.sonar.test.DbTests; | |||
import static org.mockito.Mockito.mock; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
@Category(DbTests.class) | |||
public class SearchViewComponentsActionTest { | |||
@@ -128,6 +129,19 @@ public class SearchViewComponentsActionTest { | |||
request.execute(); | |||
} | |||
@Test | |||
public void fail_when_above_max_page_size() throws Exception { | |||
thrown.expect(IllegalArgumentException.class); | |||
db.prepareDbUnit(getClass(), "shared.xml"); | |||
userSessionRule.login("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); | |||
WsTester.TestRequest request = newRequest() | |||
.setParam(Param.PAGE_SIZE, String.valueOf(MAX_LIMIT + 10)) | |||
.setParam("componentId", "EFGH") | |||
.setParam("q", "st"); | |||
request.execute(); | |||
} | |||
private WsTester.TestRequest newRequest() { | |||
return ws.newGetRequest("api/components", "search_view_components"); | |||
} |
@@ -19,7 +19,9 @@ | |||
*/ | |||
package org.sonar.server.es; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.server.search.QueryContext; | |||
import org.sonar.test.JsonAssert; | |||
@@ -31,6 +33,9 @@ import static org.junit.Assert.fail; | |||
public class SearchOptionsTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Test | |||
public void defaults() { | |||
SearchOptions options = new SearchOptions(); | |||
@@ -86,9 +91,14 @@ public class SearchOptionsTest { | |||
public void max_limit() { | |||
SearchOptions options = new SearchOptions().setLimit(42); | |||
assertThat(options.getLimit()).isEqualTo(42); | |||
} | |||
options.setLimit(SearchOptions.MAX_LIMIT + 10); | |||
assertThat(options.getLimit()).isEqualTo(QueryContext.MAX_LIMIT); | |||
@Test | |||
public void fail_when_limit_is_greater_than_max_limit() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Page size must be less than " + SearchOptions.MAX_LIMIT); | |||
new SearchOptions().setLimit(SearchOptions.MAX_LIMIT + 10); | |||
} | |||
@Test | |||
@@ -99,7 +109,7 @@ public class SearchOptionsTest { | |||
@Test | |||
public void max_page_size() { | |||
SearchOptions options = new SearchOptions().setPage(3, QueryContext.MAX_LIMIT + 10); | |||
SearchOptions options = new SearchOptions().setPage(3, QueryContext.MAX_LIMIT); | |||
assertThat(options.getOffset()).isEqualTo(QueryContext.MAX_LIMIT * 2); | |||
assertThat(options.getLimit()).isEqualTo(QueryContext.MAX_LIMIT); | |||
} |
@@ -915,22 +915,6 @@ public class IssueIndexTest { | |||
assertThat(result.getTotal()).isEqualTo(12); | |||
} | |||
@Test | |||
public void search_with_max_limit() { | |||
ComponentDto project = ComponentTesting.newProjectDto(); | |||
ComponentDto file = ComponentTesting.newFileDto(project); | |||
List<IssueDoc> issues = newArrayList(); | |||
for (int i = 0; i < 500; i++) { | |||
String key = "ISSUE" + i; | |||
issues.add(IssueTesting.newDoc(key, file)); | |||
} | |||
indexIssues(issues.toArray(new IssueDoc[] {})); | |||
IssueQuery.Builder query = IssueQuery.builder(userSessionRule); | |||
SearchResult<IssueDoc> result = index.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE)); | |||
assertThat(result.getDocs()).hasSize(SearchOptions.MAX_LIMIT); | |||
} | |||
@Test | |||
public void sort_by_status() { | |||
ComponentDto project = ComponentTesting.newProjectDto(); |
@@ -25,6 +25,7 @@ import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.security.DefaultGroups; | |||
@@ -59,12 +60,14 @@ import org.sonar.server.ws.WsTester; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchActionMediumTest { | |||
@ClassRule | |||
public static ServerTester tester = new ServerTester().withStartupTasks().withEsIndexes(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester); | |||
@@ -629,6 +632,17 @@ public class SearchActionMediumTest { | |||
result.assertJson(this.getClass(), "default_page_size_is_100.json"); | |||
} | |||
@Test | |||
public void fail_when_page_size_above_limit() throws Exception { | |||
expectedException.expect(IllegalArgumentException.class); | |||
WsTester.TestRequest request = wsTester | |||
.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) | |||
.setParam(WebService.Param.PAGE_SIZE, String.valueOf(MAX_LIMIT + 10)); | |||
request.execute(); | |||
} | |||
private RuleDto newRule() { | |||
RuleDto rule = RuleTesting.newXooX1() | |||
.setName("Rule name") |
@@ -40,18 +40,18 @@ import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDao; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.component.SnapshotDao; | |||
import org.sonar.db.component.SnapshotTesting; | |||
import org.sonar.db.measure.custom.CustomMeasureDao; | |||
import org.sonar.db.measure.custom.CustomMeasureDto; | |||
import org.sonar.db.metric.MetricDao; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.component.SnapshotTesting; | |||
import org.sonar.server.db.DbClient; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.db.measure.custom.CustomMeasureDao; | |||
import org.sonar.db.metric.MetricDao; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.user.index.UserDoc; | |||
import org.sonar.server.user.index.UserIndex; | |||
@@ -62,6 +62,7 @@ import org.sonar.server.ws.WsTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.db.measure.custom.CustomMeasureTesting.newCustomMeasureDto; | |||
import static org.sonar.db.metric.MetricTesting.newMetricDto; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
public class SearchActionTest { | |||
@@ -280,6 +281,16 @@ public class SearchActionTest { | |||
assertThat(response).contains("text-value-1"); | |||
} | |||
@Test | |||
public void fail_when_above_page_size_limit() throws Exception { | |||
expectedException.expect(IllegalArgumentException.class); | |||
newRequest() | |||
.setParam(SearchAction.PARAM_PROJECT_ID, DEFAULT_PROJECT_UUID) | |||
.setParam(WebService.Param.PAGE_SIZE, String.valueOf(MAX_LIMIT + 10)) | |||
.execute(); | |||
} | |||
private ComponentDto insertDefaultProject() { | |||
return insertProject(DEFAULT_PROJECT_UUID, DEFAULT_PROJECT_KEY); | |||
} | |||
@@ -302,7 +313,8 @@ public class SearchActionTest { | |||
} | |||
private static MetricDto newCustomMetric(String metricKey) { | |||
return newMetricDto().setEnabled(true).setUserManaged(true).setKey(metricKey).setDomain(metricKey + "-domain").setShortName(metricKey + "-name").setValueType(ValueType.STRING.name()); | |||
return newMetricDto().setEnabled(true).setUserManaged(true).setKey(metricKey).setDomain(metricKey + "-domain").setShortName(metricKey + "-name") | |||
.setValueType(ValueType.STRING.name()); | |||
} | |||
private CustomMeasureDto insertCustomMeasure(int id, MetricDto metric) { |
@@ -1,5 +0,0 @@ | |||
{ | |||
"total": 501, | |||
"p": 1, | |||
"ps": 999999 | |||
} |