Skip to content

Commit

Permalink
SONAR-6156 The search of projects on contextualized Issues page shoul…
Browse files Browse the repository at this point in the history
…d support views
  • Loading branch information
julienlancelot committed Feb 11, 2015
1 parent 43b1b11 commit 556ba81
Show file tree
Hide file tree
Showing 33 changed files with 817 additions and 139 deletions.
Expand Up @@ -103,6 +103,15 @@ public List<FilePathWithHashDto> selectModuleFilesTree(DbSession session, String
return mapper(session).selectModuleFilesTree(rootComponentUuid, Scopes.FILE);
}

public List<ComponentDto> getByIds(final DbSession session, Collection<Long> ids) {
return DaoUtils.executeLargeInputs(ids, new Function<List<Long>, List<ComponentDto>>() {
@Override
public List<ComponentDto> apply(List<Long> partition) {
return mapper(session).findByIds(partition);
}
});
}

public List<ComponentDto> getByUuids(final DbSession session, Collection<String> uuids) {
return DaoUtils.executeLargeInputs(uuids, new Function<List<String>, List<ComponentDto>>() {
@Override
Expand Down Expand Up @@ -154,4 +163,5 @@ public List<UuidWithProjectUuidDto> selectAllViewsAndSubViews(DbSession session)
public List<String> selectProjectsFromView(DbSession session, String viewUuid, String projectViewUuid) {
return mapper(session).selectProjectsFromView("%." + viewUuid + ".%", projectViewUuid);
}

}
@@ -0,0 +1,36 @@
/*
* 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.component.db;

import org.sonar.api.ServerComponent;
import org.sonar.core.component.db.ComponentIndexMapper;
import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.DbSession;

import java.util.List;

public class ComponentIndexDao implements ServerComponent, DaoComponent {

public List<Long> selectProjectIdsFromQueryAndViewOrSubViewUuid(DbSession session, String query, String viewOrSubViewUuid) {
return session.getMapper(ComponentIndexMapper.class).selectProjectIdsFromQueryAndViewOrSubViewUuid(query + "%", "%." + viewOrSubViewUuid + ".%");
}

}
Expand Up @@ -27,9 +27,11 @@
public class ComponentsWs implements WebService {

private final ComponentAppAction appAction;
private final SearchAction searchAction;

public ComponentsWs(ComponentAppAction appAction) {
public ComponentsWs(ComponentAppAction appAction, SearchAction searchAction) {
this.appAction = appAction;
this.searchAction = searchAction;
}

@Override
Expand All @@ -39,6 +41,7 @@ public void define(Context context) {
.setDescription("Components management");

appAction.define(controller);
searchAction.define(controller);
defineSuggestionsAction(controller);

controller.done();
Expand Down
@@ -0,0 +1,135 @@
/*
* 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.component.ws;

import com.google.common.collect.Sets;
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.api.web.UserRole;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.db.DbClient;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.user.UserSession;

import java.util.Collection;
import java.util.List;
import java.util.Set;

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;

public class SearchAction implements RequestHandler {

private static final short MINIMUM_SEARCH_CHARACTERS = 2;

private static final String PARAM_COMPONENT_UUID = "componentUuid";
private static final String PARAM_QUERY = "q";

private final DbClient dbClient;

public SearchAction(DbClient dbClient) {
this.dbClient = dbClient;
}

void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("search")
.setDescription("Search for components. Currently limited to projects in a view or a sub-view")
.setSince("5.1")
.setInternal(true)
.setHandler(this);

action
.createParam(PARAM_COMPONENT_UUID)
.setRequired(true)
.setDescription("View or sub view UUID")
.setExampleValue("d6d9e1e5-5e13-44fa-ab82-3ec29efa8935");

action
.createParam(PARAM_QUERY)
.setRequired(true)
.setDescription("UTF-8 search query")
.setExampleValue("sonar");

action.addPagingParams(10);
}

@Override
public void handle(Request request, Response response) {
String query = request.mandatoryParam(PARAM_QUERY);
if (query.length() < MINIMUM_SEARCH_CHARACTERS) {

This comment has been minimized.

Copy link
@mlem

mlem Aug 16, 2016

can you remove this guard? I need to fetch all components with my search, which are just projects. So I don't need any limitation on that one!

This comment has been minimized.

Copy link
@julienlancelot

julienlancelot Aug 16, 2016

Author Contributor

Sorry @mlem, but reducing the minimum number of characters could have big impact on performances.
You should use /api/projects to return all projects.

throw new IllegalArgumentException(String.format("Minimum search is %s characters", MINIMUM_SEARCH_CHARACTERS));
}
String viewOrSubUuid = request.mandatoryParam(PARAM_COMPONENT_UUID);

JsonWriter json = response.newJsonWriter();
json.beginObject();

DbSession session = dbClient.openSession(false);
try {
ComponentDto componentDto = dbClient.componentDao().getByUuid(session, viewOrSubUuid);
UserSession.get().checkProjectUuidPermission(UserRole.USER, componentDto.projectUuid());

Set<Long> projectIds = newLinkedHashSet(dbClient.componentIndexDao().selectProjectIdsFromQueryAndViewOrSubViewUuid(session, query, componentDto.uuid()));
Collection<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(session, projectIds, UserSession.get().userId(), UserRole.USER);

SearchOptions options = new SearchOptions();
options.setPage(request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE));
Set<Long> pagedProjectIds = pagedProjectIds(authorizedProjectIds, options);

List<ComponentDto> projects = dbClient.componentDao().getByIds(session, pagedProjectIds);

options.writeJson(json, authorizedProjectIds.size());
json.name("components").beginArray();
for (ComponentDto project : projects) {
json.beginObject();
json.prop("uuid", project.uuid());
json.prop("name", project.name());
json.endObject();
}
json.endArray();
} finally {
MyBatis.closeQuietly(session);
}

json.endObject();
json.close();
}

private Set<Long> pagedProjectIds(Collection<Long> projectIds, SearchOptions options) {
Set<Long> results = Sets.newLinkedHashSet();
int index = 0;
for (Long projectId : projectIds) {
if (index >= options.getOffset() && results.size() < options.getLimit()) {
results.add(projectId);
} else if (results.size() >= options.getLimit()) {
break;
}
index++;
}
return results;
}
}
Expand Up @@ -36,6 +36,7 @@
import org.sonar.core.user.AuthorizationDao;
import org.sonar.server.activity.db.ActivityDao;
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.component.db.ComponentIndexDao;
import org.sonar.server.component.db.SnapshotDao;
import org.sonar.server.computation.db.AnalysisReportDao;
import org.sonar.server.dashboard.db.DashboardDao;
Expand Down Expand Up @@ -87,6 +88,7 @@ public class DbClient implements ServerComponent {
private final WidgetPropertyDao widgetPropertyDao;
private final FileSourceDao fileSourceDao;
private final AuthorDao authorDao;
private final ComponentIndexDao componentIndexDao;

public DbClient(Database db, MyBatis myBatis, DaoComponent... daoComponents) {
this.db = db;
Expand Down Expand Up @@ -120,6 +122,7 @@ public DbClient(Database db, MyBatis myBatis, DaoComponent... daoComponents) {
widgetPropertyDao = getDao(map, WidgetPropertyDao.class);
fileSourceDao = getDao(map, FileSourceDao.class);
authorDao = getDao(map, AuthorDao.class);
componentIndexDao = getDao(map, ComponentIndexDao.class);
}

public Database database() {
Expand Down Expand Up @@ -226,6 +229,10 @@ public AuthorDao authorDao() {
return authorDao;
}

public ComponentIndexDao componentIndexDao() {
return componentIndexDao;
}

private <K> K getDao(Map<Class, DaoComponent> map, Class<K> clazz) {
return (K) map.get(clazz);
}
Expand Down
Expand Up @@ -85,6 +85,7 @@
import org.sonar.server.component.DefaultComponentFinder;
import org.sonar.server.component.DefaultRubyComponentService;
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.component.db.ComponentIndexDao;
import org.sonar.server.component.db.SnapshotDao;
import org.sonar.server.component.ws.*;
import org.sonar.server.computation.AnalysisReportQueue;
Expand Down Expand Up @@ -243,9 +244,10 @@ Collection level1Components() {
MetricDao.class,
ComponentDao.class,
SnapshotDao.class,
DbClient.class,
MeasureFilterDao.class,
AnalysisReportDao.class,
ComponentIndexDao.class,
DbClient.class,

// Elasticsearch
SearchClient.class,
Expand Down Expand Up @@ -502,11 +504,11 @@ void startLevel4Components(ComponentContainer pico) {
pico.addSingleton(DefaultComponentFinder.class);
pico.addSingleton(DefaultRubyComponentService.class);
pico.addSingleton(ComponentService.class);
pico.addSingleton(ComponentDao.class);
pico.addSingleton(ResourcesWs.class);
pico.addSingleton(ComponentsWs.class);
pico.addSingleton(ProjectsWs.class);
pico.addSingleton(ComponentAppAction.class);
pico.addSingleton(org.sonar.server.component.ws.SearchAction.class);
pico.addSingleton(EventsWs.class);
pico.addSingleton(ComponentCleanerService.class);

Expand Down
Expand Up @@ -179,6 +179,16 @@ public UserSession checkProjectPermission(String projectPermission, String proje
return this;
}

/**
* Ensures that user implies the specified project permission. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown.
*/
public UserSession checkProjectUuidPermission(String projectPermission, String projectUuid) {
if (!hasProjectPermissionByUuid(projectPermission, projectUuid)) {
throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
}
return this;
}

/**
* Does the user have the given project permission ?
*/
Expand Down Expand Up @@ -222,12 +232,11 @@ public UserSession checkComponentPermission(String projectPermission, String com
}

/**
* Does the user have the given project permission for a component ?
* Does the user have the given project permission for a component key ?
*/
public boolean hasComponentPermission(String permission, String componentKey) {
String projectKey = projectKeyByComponentKey.get(componentKey);
if (projectKey == null) {
// TODO use method using UUID
ResourceDto project = resourceDao().getRootProjectByComponentKey(componentKey);
if (project == null) {
return false;
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.platform.Server;
import org.sonar.api.web.UserRole;
import org.sonar.core.permission.GlobalPermissions;
Expand All @@ -36,9 +37,11 @@
import org.sonar.server.issue.db.IssueDao;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
import org.sonar.test.DbTests;

import static org.mockito.Mockito.mock;

@Category(DbTests.class)
public class IssuesActionTest {

private final static String PROJECT_KEY = "struts";
Expand Down
Expand Up @@ -163,6 +163,27 @@ public void get_by_keys() {
assertThat(dao.getByKeys(session, "unknown")).isEmpty();
}

@Test
public void get_by_ids() {
setupData("shared");

List<ComponentDto> results = dao.getByIds(session, newArrayList(4L));
assertThat(results).hasSize(1);

ComponentDto result = results.get(0);
assertThat(result).isNotNull();
assertThat(result.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java");
assertThat(result.path()).isEqualTo("src/org/struts/RequestContext.java");
assertThat(result.name()).isEqualTo("RequestContext.java");
assertThat(result.longName()).isEqualTo("org.struts.RequestContext");
assertThat(result.qualifier()).isEqualTo("FIL");
assertThat(result.scope()).isEqualTo("FIL");
assertThat(result.language()).isEqualTo("java");
assertThat(result.parentProjectId()).isEqualTo(2);

assertThat(dao.getByIds(session, newArrayList(555L))).isEmpty();
}

@Test
public void get_by_uuids() {
setupData("shared");
Expand Down

0 comments on commit 556ba81

Please sign in to comment.