From: Stephane Gamard Date: Wed, 10 Sep 2014 13:40:33 +0000 (+0200) Subject: SONAR-5530 - Using *Finders for context objects in IssueResult X-Git-Tag: 5.0-RC1~1040 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=157eeb5f152df3485781372ddf772785d0f193de;p=sonarqube.git SONAR-5530 - Using *Finders for context objects in IssueResult --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java b/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java index 12e48a0e914..a16f092c6d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java @@ -155,12 +155,12 @@ public abstract class BaseDao, KEY extends Serializ return value; } - public Collection getByKeys(DbSession session, KEY... keys) { + public List getByKeys(DbSession session, KEY... keys) { return getByKeys(session, ImmutableList.copyOf(keys)); } - public Collection getByKeys(DbSession session, Collection keys) { - Collection results = new ArrayList(); + public List getByKeys(DbSession session, Collection keys) { + List results = new ArrayList(); for (KEY key : keys) { results.add(this.getByKey(session, key)); } @@ -306,7 +306,7 @@ public abstract class BaseDao, KEY extends Serializ // Synchronization methods - protected ResultHandler getSynchronizationResultHandler(final DbSession session){ + protected ResultHandler getSynchronizationResultHandler(final DbSession session) { return new ResultHandler() { @Override public void handleResult(ResultContext resultContext) { @@ -316,7 +316,7 @@ public abstract class BaseDao, KEY extends Serializ }; } - protected Map getSynchronizationParams(Date date){ + protected Map getSynchronizationParams(Date date) { Map params = newHashMap(); params.put("date", new Timestamp(date.getTime())); return params; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java index 4ab13b1b6f1..0337ce92174 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java @@ -24,7 +24,6 @@ import com.google.common.base.Strings; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import org.apache.commons.lang.StringUtils; -import org.elasticsearch.action.search.SearchResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.ServerComponent; @@ -40,7 +39,6 @@ import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.user.User; import org.sonar.api.user.UserFinder; -import org.sonar.api.utils.Paging; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultIssueBuilder; import org.sonar.core.issue.IssueNotifications; @@ -59,15 +57,16 @@ import org.sonar.core.user.AuthorizationDao; import org.sonar.server.db.DbClient; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.issue.index.IssueResult; -import org.sonar.server.rule.index.RuleIndex; import org.sonar.server.search.IndexClient; import org.sonar.server.search.QueryContext; import org.sonar.server.user.UserSession; import javax.annotation.Nullable; - -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; /** * @since 3.6 @@ -335,46 +334,11 @@ public class IssueService implements ServerComponent { return indexClient.get(IssueIndex.class).getByKey(key); } - public IssueResult search(IssueQuery query, QueryContext options) { + public org.sonar.server.search.Result search(IssueQuery query, QueryContext options) { IssueIndex issueIndex = indexClient.get(IssueIndex.class); - SearchResponse esResults = issueIndex.search(query, options); - - // Extend the content of the resultSet to make an actual IssueResponse - IssueResult result = new IssueResult(issueIndex, esResults); - result.setPaging(Paging.create( - options.getLimit(), - (options.getOffset() * options.getLimit()) + 1, - new Long(esResults.getHits().getTotalHits()).intValue())); - - // Insert the projects and component name; - Set ruleKeys = new HashSet(); - Set projectKeys = new HashSet(); - Set componentKeys = new HashSet(); - Set actionPlanKeys = new HashSet(); - List userLogins = new ArrayList(); - - DbSession session = dbClient.openSession(false); - for (Issue issue : result.getHits()) { - ruleKeys.add(issue.ruleKey()); - projectKeys.add(issue.projectKey()); - componentKeys.add(issue.componentKey()); - actionPlanKeys.add(issue.actionPlanKey()); - userLogins.add(issue.authorLogin()); - } - - try { - // TODO Rule vs Rule problem - indexClient.get(RuleIndex.class).getByKeys(ruleKeys); - result.addProjects(dbClient.componentDao().getByKeys(session, projectKeys)); - result.addComponents(dbClient.componentDao().getByKeys(session, componentKeys)); - result.addUsers(userFinder.findByLogins(userLogins)); - result.addActionPlans(actionPlanService.findByKeys(actionPlanKeys)); - } finally { - session.close(); - } + return issueIndex.search(query, options); - return result; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java index a58b008a963..cd6535ea6bb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.*; import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueQuery; import org.sonar.api.web.UserRole; import org.sonar.core.issue.db.IssueDto; @@ -39,7 +40,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -public class IssueIndex extends BaseIndex { +public class IssueIndex extends BaseIndex { public IssueIndex(IssueNormalizer normalizer, SearchClient client) { super(IndexDefinition.ISSUES, normalizer, client); @@ -108,7 +109,7 @@ public class IssueIndex extends BaseIndex { return new IssueDoc(fields); } - public SearchResponse search(IssueQuery query, QueryContext options) { + public Result search(IssueQuery query, QueryContext options) { SearchRequestBuilder esSearch = getClient() .prepareSearch(this.getIndexName()) @@ -207,7 +208,8 @@ public class IssueIndex extends BaseIndex { // esSearch.addAggregation(AggregationBuilders.sum("totalDuration") // .field(IssueNormalizer.IssueField.DEBT.field())); - return getClient().execute(esSearch); + SearchResponse response = getClient().execute(esSearch); + return new Result(this, response); } private void matchFilter(BoolFilterBuilder filter, IndexField field, Collection values) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java index 66a7efe38a8..faef9948524 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResult.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +@Deprecated public class IssueResult extends Result implements IssueQueryResult { private final Map rules; @@ -145,7 +146,7 @@ public class IssueResult extends Result implements IssueQueryResult { public void addComponents(Collection components) { for (ComponentDto component : components) { this.components.put(component.key(), component); - } + } } public void addUsers(Collection users) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index f19c3248a8e..82638f6dda6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -19,46 +19,56 @@ */ package org.sonar.server.issue.ws; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.io.Resources; -import org.sonar.api.component.Component; import org.sonar.api.i18n.I18n; -import org.sonar.api.issue.*; +import org.sonar.api.issue.ActionPlan; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.IssueComment; +import org.sonar.api.issue.IssueQuery; +import org.sonar.api.issue.IssueQueryResult; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; -import org.sonar.api.rules.Rule; import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.RequestHandler; -import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.user.User; +import org.sonar.api.user.UserFinder; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.web.UserRole; import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; import org.sonar.markdown.Markdown; +import org.sonar.server.component.DefaultComponentFinder; +import org.sonar.server.db.DbClient; import org.sonar.server.issue.IssueService; +import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.filter.IssueFilterParameters; -import org.sonar.server.issue.index.IssueResult; -import org.sonar.server.search.FacetValue; +import org.sonar.server.rule.Rule; +import org.sonar.server.rule.RuleService; import org.sonar.server.search.QueryContext; import org.sonar.server.search.Result; -import org.sonar.server.search.ws.SearchOptions; +import org.sonar.server.search.ws.SearchRequestHandler; import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static com.google.common.collect.Lists.newArrayList; -public class SearchAction implements RequestHandler { +public class SearchAction extends SearchRequestHandler { public static final String SEARCH_ACTION = "search"; @@ -70,45 +80,40 @@ public class SearchAction implements RequestHandler { private static final String EXTRA_FIELDS_PARAM = "extra_fields"; - public static final String PARAM_FACETS = "facets"; - private final IssueService service; private final IssueActionsWriter actionsWriter; + + private final RuleService ruleService; + private final DbClient dbClient; + private final DefaultComponentFinder componentFinder; + private final ActionPlanService actionPlanService; + private final UserFinder userFinder; private final I18n i18n; private final Durations durations; - public SearchAction(IssueService service, IssueActionsWriter actionsWriter, I18n i18n, Durations durations) { + public SearchAction(IssueService service, IssueActionsWriter actionsWriter, RuleService ruleService, DbClient dbClient, DefaultComponentFinder componentFinder, + ActionPlanService actionPlanService, UserFinder userFinder, I18n i18n, Durations durations) { + super(SEARCH_ACTION); this.service = service; this.actionsWriter = actionsWriter; + this.ruleService = ruleService; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + this.actionPlanService = actionPlanService; + this.userFinder = userFinder; this.i18n = i18n; this.durations = durations; } - void define(WebService.NewController controller) { - WebService.NewAction action = controller.createAction(SEARCH_ACTION) - .setDescription("Get a list of issues. If the number of issues is greater than 10,000, only the first 10,000 ones are returned by the web service. " + - "Requires Browse permission on project(s)") + @Override + protected void doDefinition(WebService.NewAction action) { + action.setDescription("Get a list of issues. If the number of issues is greater than 10,000, " + + "only the first 10,000 ones are returned by the web service. " + + "Requires Browse permission on project(s)") .setSince("3.6") .setHandler(this) .setResponseExample(Resources.getResource(this.getClass(), "example-search.json")); - // Add globalized search options. Will also support legacy params - // Generic search parameters - SearchOptions.definePageParams(action); - SearchOptions.defineFieldsParam(action, Collections.emptyList()); - - // Issue-specific search parameters - defineIssueSearchParameters(action); - - // Other parameters - action.createParam(PARAM_FACETS) - .setDescription("Compute predefined facets") - .setBooleanPossibleValues() - .setDefaultValue("false"); - } - - public static void defineIssueSearchParameters(WebService.NewAction action) { - action.createParam(IssueFilterParameters.ISSUES) .setDescription("Comma-separated list of issue keys") .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); @@ -192,72 +197,114 @@ public class SearchAction implements RequestHandler { } @Override - public void handle(Request request, Response response) { - IssueQuery query = createQuery(request); - SearchOptions searchOptions = SearchOptions.create(request); - QueryContext queryContext = new QueryContext(); - queryContext.setPage(searchOptions.page(), searchOptions.pageSize()); - queryContext.setFacet(request.mandatoryParamAsBoolean(PARAM_FACETS)); + protected IssueQuery doQuery(Request request) { + IssueQuery.Builder builder = IssueQuery.builder() + .requiredRole(UserRole.USER) + .issueKeys(request.paramAsStrings(IssueFilterParameters.ISSUES)) + .severities(request.paramAsStrings(IssueFilterParameters.SEVERITIES)) + .statuses(request.paramAsStrings(IssueFilterParameters.STATUSES)) + .resolutions(request.paramAsStrings(IssueFilterParameters.RESOLUTIONS)) + .resolved(request.paramAsBoolean(IssueFilterParameters.RESOLVED)) + .components(request.paramAsStrings(IssueFilterParameters.COMPONENTS)) + .componentRoots(request.paramAsStrings(IssueFilterParameters.COMPONENT_ROOTS)) + .rules(stringsToRules(request.paramAsStrings(IssueFilterParameters.RULES))) + .actionPlans(request.paramAsStrings(IssueFilterParameters.ACTION_PLANS)) + .reporters(request.paramAsStrings(IssueFilterParameters.REPORTERS)) + .assignees(request.paramAsStrings(IssueFilterParameters.ASSIGNEES)) + .languages(request.paramAsStrings(IssueFilterParameters.LANGUAGES)) + .assigned(request.paramAsBoolean(IssueFilterParameters.ASSIGNED)) + .planned(request.paramAsBoolean(IssueFilterParameters.PLANNED)) + .hideRules(request.paramAsBoolean(IssueFilterParameters.HIDE_RULES)) + .createdAt(request.paramAsDateTime(IssueFilterParameters.CREATED_AT)) + .createdAfter(request.paramAsDateTime(IssueFilterParameters.CREATED_AFTER)) + .createdBefore(request.paramAsDateTime(IssueFilterParameters.CREATED_BEFORE)) + .pageSize(request.paramAsInt(IssueFilterParameters.PAGE_SIZE)) + .pageIndex(request.paramAsInt(IssueFilterParameters.PAGE_INDEX)); + String sort = request.param(IssueFilterParameters.SORT); + if (!Strings.isNullOrEmpty(sort)) { + builder.sort(sort); + builder.asc(request.paramAsBoolean(IssueFilterParameters.ASC)); + } + return builder.build(); + } - IssueResult results = service.search(query, queryContext); + @Override + protected Result doSearch(IssueQuery query, QueryContext context) { + return service.search(query, context); + } - JsonWriter json = response.newJsonWriter(); - json.beginObject(); + @Override + protected void doResultResponse(Request request, QueryContext context, Result result, JsonWriter json) { + writeIssues(result, request.paramAsStrings(EXTRA_FIELDS_PARAM), json); + } - writePaging(results, json); - writeIssues(results, request.paramAsStrings(EXTRA_FIELDS_PARAM), json); + @Override + protected void doContextResponse(Request request, QueryContext context, Result result, JsonWriter json) { + + // Insert the projects and component name; + Set ruleKeys = new HashSet(); + Set projectKeys = new HashSet(); + Set componentKeys = new HashSet(); + Set actionPlanKeys = new HashSet(); + List userLogins = new ArrayList(); + // + // DbSession session = dbClient.openSession(false); + for (Issue issue : result.getHits()) { + ruleKeys.add(issue.ruleKey()); + projectKeys.add(issue.projectKey()); + componentKeys.add(issue.componentKey()); + actionPlanKeys.add(issue.actionPlanKey()); + userLogins.add(issue.authorLogin()); + } - // TODO normalize component name for snippet -- Mighty change over time (file move) - writeComponents(results, json); - // TODO normalize project Name for snippet -- Carefull might change over time (Project Rename) - writeProjects(results, json); + writeRules(json, ruleService.getByKeys(ruleKeys)); + writeUsers(json, userFinder.findByLogins(userLogins)); + writeActionPlans(json, actionPlanService.findByKeys(actionPlanKeys)); - // TODO Not certain that this is required (Legacy) - // writeRules(results, json); - // writeUsers(results, json); - // writeActionPlans(results, json); + DbSession session = dbClient.openSession(false); + try { + writeProjects(json, dbClient.componentDao().getByKeys(session, projectKeys)); + writeComponents(json, dbClient.componentDao().getByKeys(session, componentKeys)); + } finally { + session.close(); + } - if (queryContext.isFacet()) { - writeFacets(results, json); - } + // TODO remove legacy paging. Handled by the SearchRequestHandler + writeLegacyPaging(context, json, result); + } - json.endObject().close(); + private void writeLegacyPaging(QueryContext context, JsonWriter json, Result result) { + // TODO remove with stas on HTML side + json.prop("maxResultsReached", false); + json.name("paging").beginObject() + .prop("pageIndex", context.getPage()) + .prop("pageSize", context.getLimit()) + .prop("total", result.getTotal()) + // TODO Remove as part of Front-end rework on Issue Domain + .prop("fTotal", i18n.formatInteger(UserSession.get().locale(), (int) result.getTotal())) + .prop("pages", Math.ceil(result.getTotal() / (context.getLimit() * 1.0))) + .endObject(); } - private void writeFacets(Result results, JsonWriter json) { - json.name("facets").beginArray(); - for (Map.Entry> facet : results.getFacets().entrySet()) { - json.beginObject(); - json.prop("property", facet.getKey()); - json.name("values").beginArray(); - for (FacetValue facetValue : facet.getValue()) { - json.beginObject(); - json.prop("val", facetValue.getKey()); - json.prop("count", facetValue.getValue()); - json.endObject(); - } - json.endArray().endObject(); + // TODO change to use the RuleMapper + private void writeRules(JsonWriter json, Collection rules) { + json.name("rules").beginArray(); + for (Rule rule : rules) { + json.beginObject() + .prop("key", rule.key().toString()) + .prop("name", rule.name()) + .prop("desc", rule.htmlDescription()) + .prop("status", rule.status().toString()) + .endObject(); } json.endArray(); } - private void writePaging(IssueQueryResult result, JsonWriter json) { - json.prop("maxResultsReached", result.maxResultsReached()); - json.name("paging").beginObject() - .prop("pageIndex", result.paging().pageIndex()) - .prop("pageSize", result.paging().pageSize()) - .prop("total", result.paging().total()) - // TODO Remove as part of Front-end rework on Issue Domain - .prop("fTotal", i18n.formatInteger(UserSession.get().locale(), result.paging().total())) - .prop("pages", result.paging().pages()) - .endObject(); - } + private void writeIssues(Result result, @Nullable List extraFields, JsonWriter json) { + json.name("issues").beginArray(); - private void writeIssues(IssueQueryResult result, @Nullable List extraFields, JsonWriter json) { - json.name("issues").beginArray(); - - for (Issue issue : result.issues()) { - json.beginObject(); + for (Issue issue : result.getHits()) { + json.beginObject(); String actionPlanKey = issue.actionPlanKey(); Duration debt = issue.debt(); @@ -284,9 +331,11 @@ public class SearchAction implements RequestHandler { .prop("fUpdateAge", formatAgeDate(updateDate)) .prop("closeDate", isoDate(issue.closeDate())); - writeIssueComments(result, issue, json); + // TODO add comments + // writeIssueComments(result, issue, json); writeIssueAttributes(issue, json); - writeIssueExtraFields(result, issue, extraFields, json); + // TODO Add fields + // writeIssueExtraFields(result, issue, extraFields, json); json.endObject(); } @@ -354,32 +403,30 @@ public class SearchAction implements RequestHandler { } } - private void writeComponents(IssueQueryResult result, JsonWriter json) { + private void writeComponents(JsonWriter json, List components) { json.name("components").beginArray(); - for (Component component : result.components()) { - ComponentDto componentDto = (ComponentDto) component; + for (ComponentDto component : components) { json.beginObject() .prop("key", component.key()) - .prop("id", componentDto.getId()) - .prop("qualifier", component.qualifier()) + .prop("id", component.getId()) + .prop("qualifier", component.qualifier()) .prop("name", component.name()) .prop("longName", component.longName()) .prop("path", component.path()) // On a root project, subProjectId is null but projectId is equal to itself, which make no sense. - .prop("projectId", (componentDto.projectId() != null && componentDto.subProjectId() != null) ? componentDto.projectId() : null) - .prop("subProjectId", componentDto.subProjectId()) + .prop("projectId", (component.projectId() != null && component.subProjectId() != null) ? component.projectId() : null) + .prop("subProjectId", component.subProjectId()) .endObject(); } json.endArray(); } - private void writeProjects(IssueQueryResult result, JsonWriter json) { + private void writeProjects(JsonWriter json, List projects) { json.name("projects").beginArray(); - for (Component project : result.projects()) { - ComponentDto componentDto = (ComponentDto) project; + for (ComponentDto project : projects) { json.beginObject() .prop("key", project.key()) - .prop("id", componentDto.getId()) + .prop("id", project.getId()) .prop("qualifier", project.qualifier()) .prop("name", project.name()) .prop("longName", project.longName()) @@ -388,22 +435,9 @@ public class SearchAction implements RequestHandler { json.endArray(); } - private void writeRules(IssueQueryResult result, JsonWriter json) { - json.name("rules").beginArray(); - for (Rule rule : result.rules()) { - json.beginObject() - .prop("key", rule.ruleKey().toString()) - .prop("name", rule.getName()) - .prop("desc", rule.getDescription()) - .prop("status", rule.getStatus()) - .endObject(); - } - json.endArray(); - } - - private void writeUsers(IssueQueryResult result, JsonWriter json) { + private void writeUsers(JsonWriter json, List users) { json.name("users").beginArray(); - for (User user : result.users()) { + for (User user : users) { json.beginObject() .prop("login", user.login()) .prop("name", user.name()) @@ -414,10 +448,10 @@ public class SearchAction implements RequestHandler { json.endArray(); } - private void writeActionPlans(IssueQueryResult result, JsonWriter json) { - if (!result.actionPlans().isEmpty()) { + private void writeActionPlans(JsonWriter json, List plans) { + if (!plans.isEmpty()) { json.name("actionPlans").beginArray(); - for (ActionPlan actionPlan : result.actionPlans()) { + for (ActionPlan actionPlan : plans) { Date deadLine = actionPlan.deadLine(); Date updatedAt = actionPlan.updatedAt(); @@ -475,36 +509,4 @@ public class SearchAction implements RequestHandler { } return null; } - - @VisibleForTesting - static IssueQuery createQuery(Request request) { - IssueQuery.Builder builder = IssueQuery.builder() - .requiredRole(UserRole.USER) - .issueKeys(request.paramAsStrings(IssueFilterParameters.ISSUES)) - .severities(request.paramAsStrings(IssueFilterParameters.SEVERITIES)) - .statuses(request.paramAsStrings(IssueFilterParameters.STATUSES)) - .resolutions(request.paramAsStrings(IssueFilterParameters.RESOLUTIONS)) - .resolved(request.paramAsBoolean(IssueFilterParameters.RESOLVED)) - .components(request.paramAsStrings(IssueFilterParameters.COMPONENTS)) - .componentRoots(request.paramAsStrings(IssueFilterParameters.COMPONENT_ROOTS)) - .rules(stringsToRules(request.paramAsStrings(IssueFilterParameters.RULES))) - .actionPlans(request.paramAsStrings(IssueFilterParameters.ACTION_PLANS)) - .reporters(request.paramAsStrings(IssueFilterParameters.REPORTERS)) - .assignees(request.paramAsStrings(IssueFilterParameters.ASSIGNEES)) - .languages(request.paramAsStrings(IssueFilterParameters.LANGUAGES)) - .assigned(request.paramAsBoolean(IssueFilterParameters.ASSIGNED)) - .planned(request.paramAsBoolean(IssueFilterParameters.PLANNED)) - .hideRules(request.paramAsBoolean(IssueFilterParameters.HIDE_RULES)) - .createdAt(request.paramAsDateTime(IssueFilterParameters.CREATED_AT)) - .createdAfter(request.paramAsDateTime(IssueFilterParameters.CREATED_AFTER)) - .createdBefore(request.paramAsDateTime(IssueFilterParameters.CREATED_BEFORE)) - .pageSize(request.paramAsInt(IssueFilterParameters.PAGE_SIZE)) - .pageIndex(request.paramAsInt(IssueFilterParameters.PAGE_INDEX)); - String sort = request.param(IssueFilterParameters.SORT); - if (!Strings.isNullOrEmpty(sort)) { - builder.sort(sort); - builder.asc(request.paramAsBoolean(IssueFilterParameters.ASC)); - } - return builder.build(); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java index 85fab0b327a..45850dd4dcd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java @@ -32,6 +32,8 @@ import org.sonar.server.user.UserSession; import javax.annotation.CheckForNull; +import java.util.Collection; +import java.util.List; import java.util.Set; /** @@ -56,6 +58,10 @@ public class RuleService implements ServerComponent { return index.getByKey(key); } + public List getByKeys(Collection keys) { + return index.getByKeys(keys); + } + public Rule getNonNullByKey(RuleKey key) { Rule rule = index.getByKey(key); if (rule == null) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/BaseIndex.java b/server/sonar-server/src/main/java/org/sonar/server/search/BaseIndex.java index 948b5e8d8f8..64a11932dfa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/BaseIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/BaseIndex.java @@ -401,7 +401,7 @@ public abstract class BaseIndex, KEY extends Serial return null; } - public Collection getByKeys(Collection keys) { + public List getByKeys(Collection keys) { List results = new ArrayList(); MultiGetRequestBuilder request = client.prepareMultiGet() .setPreference("_local"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/QueryContext.java b/server/sonar-server/src/main/java/org/sonar/server/search/QueryContext.java index a8dda8755d4..b2cfae1854b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/QueryContext.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/QueryContext.java @@ -112,6 +112,11 @@ public class QueryContext { return this; } + public int getPage() { + double page = new Double(getOffset() + 1) / new Double(getLimit()); + return (int) Math.ceil(page); + } + /** * Limit on the number of results to return. Defaults to {@link #DEFAULT_LIMIT}. */ @@ -162,5 +167,4 @@ public class QueryContext { public Set getUserGroups() { return userGroups; } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java b/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java new file mode 100644 index 00000000000..52b89835942 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java @@ -0,0 +1,156 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.search.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.RequestHandler; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.server.search.FacetValue; +import org.sonar.server.search.QueryContext; +import org.sonar.server.search.Result; + +import javax.annotation.CheckForNull; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public abstract class SearchRequestHandler implements RequestHandler { + + public static final String PARAM_TEXT_QUERY = "q"; + public static final String PARAM_PAGE = "p"; + public static final String PARAM_PAGE_SIZE = "ps"; + public static final String PARAM_FIELDS = "f"; + public static final String PARAM_SORT = "s"; + public static final String PARAM_ASCENDING = "asc"; + + public static final String PARAM_FACETS = "facets"; + + private int pageSize; + private int page; + private List fields; + + private final String actionName; + + /** + * The fields to be returned in JSON response. null means that + * all the fields must be returned. + */ + @CheckForNull + public List fields() { + return fields; + } + + protected SearchRequestHandler(String actionName) { + this.actionName = actionName; + } + + protected abstract Result doSearch(QUERY query, QueryContext context); + + protected abstract QUERY doQuery(Request request); + + protected abstract void doResultResponse(Request request, QueryContext context, Result result, JsonWriter json); + + protected abstract void doContextResponse(Request request, QueryContext context, Result result, JsonWriter json); + + protected abstract void doDefinition(WebService.NewAction action); + + public final void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction(this.actionName); + + action + .createParam(PARAM_PAGE) + .setDescription("1-based page number") + .setExampleValue("42") + .setDefaultValue("1"); + + action + .createParam(PARAM_PAGE_SIZE) + .setDescription("Page size. Must be greater than 0.") + .setExampleValue("20") + .setDefaultValue(String.valueOf(QueryContext.DEFAULT_LIMIT)); + + action.createParam(PARAM_FACETS) + .setDescription("Compute predefined facets") + .setBooleanPossibleValues() + .setDefaultValue("false"); + + WebService.NewParam newParam = action + .createParam(PARAM_FIELDS) + .setDescription("Comma-separated list of the fields to be returned in response. All the fields are returned by default."); + + // .setPossibleValues(possibleFields); + // if (possibleFields != null && possibleFields.size() > 1) { + // Iterator it = possibleFields.iterator(); + // newParam.setExampleValue(String.format("%s,%s", it.next(), it.next())); + // } + + this.doDefinition(action); + } + + @Override + public final void handle(Request request, Response response) throws Exception { + QueryContext context = getQueryContext(request); + QUERY query = doQuery(request); + Result result = doSearch(query, context); + + JsonWriter json = response.newJsonWriter().beginObject(); + this.writeStatistics(json, result); + doResultResponse(request, context, result, json); + doContextResponse(request, context, result, json); + if (context.isFacet()) { + writeFacets(result, json); + } + json.endObject().close(); + } + + private final QueryContext getQueryContext(Request request) { + return new QueryContext() + .addFieldsToReturn(request.paramAsStrings(PARAM_FIELDS)) + .setFacet(request.paramAsBoolean(PARAM_FACETS)) + .setPage(request.mandatoryParamAsInt(PARAM_PAGE), + request.mandatoryParamAsInt(PARAM_PAGE_SIZE)); + } + + protected void writeStatistics(JsonWriter json, Result searchResult) { + json.prop("total", searchResult.getTotal()); + json.prop(PARAM_PAGE, page); + json.prop(PARAM_PAGE_SIZE, pageSize); + } + + protected void writeFacets(Result results, JsonWriter json) { + json.name("facets").beginArray(); + for (Map.Entry> facet : results.getFacets().entrySet()) { + json.beginObject(); + json.prop("property", facet.getKey()); + json.name("values").beginArray(); + for (FacetValue facetValue : facet.getValue()) { + json.beginObject(); + json.prop("val", facetValue.getKey()); + json.prop("count", facetValue.getValue()); + json.endObject(); + } + json.endArray().endObject(); + } + json.endArray(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java index 40813271558..b6fb6e7dc62 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java @@ -23,6 +23,7 @@ import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueQuery; import org.sonar.api.security.DefaultGroups; import org.sonar.api.utils.DateUtils; @@ -35,7 +36,6 @@ import org.sonar.core.rule.RuleDto; import org.sonar.server.component.persistence.ComponentDao; import org.sonar.server.db.DbClient; import org.sonar.server.issue.db.IssueDao; -import org.sonar.server.issue.index.IssueResult; import org.sonar.server.rule.RuleTesting; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.search.QueryContext; @@ -102,7 +102,7 @@ public class IssueServiceMediumTest { tester.get(IssueDao.class).insert(session, issue1, issue2); session.commit(); - IssueResult result = service.search(IssueQuery.builder().build(), new QueryContext()); + org.sonar.server.search.Result result = service.search(IssueQuery.builder().build(), new QueryContext()); assertThat(result.getHits()).hasSize(2); assertThat(result.getFacets()).isEmpty(); @@ -111,18 +111,6 @@ public class IssueServiceMediumTest { assertThat(result.getFacetKeys("actionPlan")).hasSize(2); } - @Test - public void has_component_and_project() throws Exception { - IssueDto issue1 = getIssue().setActionPlanKey("P1"); - IssueDto issue2 = getIssue().setActionPlanKey("P2"); - tester.get(IssueDao.class).insert(session, issue1, issue2); - session.commit(); - - IssueResult result = service.search(IssueQuery.builder().build(), new QueryContext()); - assertThat(result.projects()).hasSize(1); - assertThat(result.components()).hasSize(1); - } - private IssueDto getIssue() { return new IssueDto() .setIssueCreationDate(DateUtils.parseDate("2014-09-04")) diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueBackendMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueBackendMediumTest.java index 19837277ed6..0d667e41a2e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueBackendMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueBackendMediumTest.java @@ -24,6 +24,7 @@ import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; import org.sonar.core.component.ComponentDto; import org.sonar.core.issue.db.IssueDto; @@ -31,7 +32,6 @@ import org.sonar.core.persistence.DbSession; import org.sonar.core.rule.RuleDto; import org.sonar.server.component.persistence.ComponentDao; import org.sonar.server.db.DbClient; -import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.platform.Platform; import org.sonar.server.rule.RuleTesting; @@ -104,7 +104,7 @@ public class IssueBackendMediumTest { assertThat(indexClient.get(IssueIndex.class).countAll()).isEqualTo(1); // should find by key - IssueDoc issueDoc = indexClient.get(IssueIndex.class).getByKey(issue.getKey()); + Issue issueDoc = indexClient.get(IssueIndex.class).getByKey(issue.getKey()); assertThat(issueDoc).isNotNull(); // Check all normalized fields diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java index 7a52af104c4..cf8058d72cd 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java @@ -20,11 +20,11 @@ package org.sonar.server.issue.index; import com.google.common.collect.ImmutableList; -import org.elasticsearch.action.search.SearchResponse; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.IssueQuery; import org.sonar.api.security.DefaultGroups; import org.sonar.api.utils.DateUtils; @@ -41,6 +41,7 @@ import org.sonar.server.db.DbClient; import org.sonar.server.rule.RuleTesting; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.search.QueryContext; +import org.sonar.server.search.Result; import org.sonar.server.tester.ServerTester; import org.sonar.server.user.MockUserSession; @@ -112,19 +113,19 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); query.actionPlans(ImmutableList.of(plan1)); - SearchResponse result = index.search(query.build(), new QueryContext()); + Result result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(1L); + assertThat(result.getHits()).hasSize(1); query = IssueQuery.builder(); query.actionPlans(ImmutableList.of(plan2)); result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(1L); + assertThat(result.getHits()).hasSize(1); query = IssueQuery.builder(); query.actionPlans(ImmutableList.of(plan2, plan1)); result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(2L); + assertThat(result.getHits()).hasSize(2); } @Test @@ -137,13 +138,13 @@ public class IssueIndexMediumTest { session.commit(); IssueQuery.Builder query = IssueQuery.builder(); - SearchResponse result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(2L); + Result result = index.search(query.build(), new QueryContext()); + assertThat(result.getHits()).hasSize(2); query = IssueQuery.builder(); query.assigned(true); result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(1L); + assertThat(result.getHits()).hasSize(1); } @Test @@ -159,18 +160,18 @@ public class IssueIndexMediumTest { session.commit(); IssueQuery.Builder query = IssueQuery.builder(); - SearchResponse result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(3L); + Result result = index.search(query.build(), new QueryContext()); + assertThat(result.getHits()).hasSize(3); query = IssueQuery.builder(); query.assignees(ImmutableList.of(assignee1)); result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(1L); + assertThat(result.getHits()).hasSize(1); query = IssueQuery.builder(); query.assignees(ImmutableList.of(assignee1, assignee2)); result = index.search(query.build(), new QueryContext()); - assertThat(result.getHits().getTotalHits()).isEqualTo(2L); + assertThat(result.getHits()).hasSize(2); } @Test @@ -197,7 +198,6 @@ public class IssueIndexMediumTest { db.groupDao().insert(session, groupDto); tester.get(PermissionFacade.class).insertGroupPermission(project2.getId(), groupDto.getName(), UserRole.USER, session); - db.issueAuthorizationDao().synchronizeAfter(session, new Date(0)); session.commit(); @@ -206,16 +206,16 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); MockUserSession.set().setUserGroups("sonar-users"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(1L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); MockUserSession.set().setUserGroups("sonar-admins"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(1L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); MockUserSession.set().setUserGroups("sonar-users", "sonar-admins"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(2L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(2); MockUserSession.set().setUserGroups("another group"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(0); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(0); } @Test @@ -250,13 +250,13 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); MockUserSession.set().setLogin("john"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(1L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); MockUserSession.set().setLogin("max"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(1L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); MockUserSession.set().setLogin("another guy"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(0L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(0); } @Test @@ -291,7 +291,7 @@ public class IssueIndexMediumTest { IssueQuery.Builder query = IssueQuery.builder(); MockUserSession.set().setLogin("john").setUserGroups("sonar-users"); - assertThat(index.search(query.build(), new QueryContext()).getHits().getTotalHits()).isEqualTo(1L); + assertThat(index.search(query.build(), new QueryContext()).getHits()).hasSize(1); } private IssueDto createIssue() {