Skip to content

Commit

Permalink
SONAR-6521 Add internal WS to list user details for batch
Browse files Browse the repository at this point in the history
  • Loading branch information
jblievremont committed May 5, 2015
1 parent cf4b511 commit cad73b7
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 30 deletions.
@@ -0,0 +1,27 @@
/*
* 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.batch;

import org.sonar.server.ws.WsAction;

public interface BatchAction extends WsAction {

// Marker interface
}
Expand Up @@ -33,15 +33,11 @@ public class BatchWs implements WebService {
public static final String API_ENDPOINT = "batch";

private final BatchIndex batchIndex;
private final GlobalRepositoryAction globalRepositoryAction;
private final ProjectRepositoryAction projectRepositoryAction;
private final IssuesAction issuesAction;
private final BatchAction[] actions;

public BatchWs(BatchIndex batchIndex, GlobalRepositoryAction globalRepositoryAction, ProjectRepositoryAction projectRepositoryAction, IssuesAction issuesAction) {
public BatchWs(BatchIndex batchIndex, BatchAction... actions) {
this.batchIndex = batchIndex;
this.globalRepositoryAction = globalRepositoryAction;
this.projectRepositoryAction = projectRepositoryAction;
this.issuesAction = issuesAction;
this.actions = actions;
}

@Override
Expand All @@ -52,9 +48,9 @@ public void define(Context context) {

defineIndexAction(controller);
defineFileAction(controller);
globalRepositoryAction.define(controller);
projectRepositoryAction.define(controller);
issuesAction.define(controller);
for (BatchAction action : actions) {
action.define(controller);
}

controller.done();
}
Expand Down
Expand Up @@ -22,7 +22,6 @@

import org.apache.commons.io.IOUtils;
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.batch.protocol.input.GlobalRepositories;
Expand All @@ -37,7 +36,7 @@
import org.sonar.server.plugins.MimeTypes;
import org.sonar.server.user.UserSession;

public class GlobalRepositoryAction implements RequestHandler {
public class GlobalRepositoryAction implements BatchAction {

private final DbClient dbClient;
private final PropertiesDao propertiesDao;
Expand All @@ -47,7 +46,8 @@ public GlobalRepositoryAction(DbClient dbClient, PropertiesDao propertiesDao) {
this.propertiesDao = propertiesDao;
}

void define(WebService.NewController controller) {
@Override
public void define(WebService.NewController controller) {
controller.createAction("global")
.setDescription("Return metrics and global properties")
.setSince("4.5")
Expand Down
Expand Up @@ -22,7 +22,6 @@

import org.sonar.api.resources.Scopes;
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.batch.protocol.input.BatchInput;
Expand All @@ -44,7 +43,7 @@

import static com.google.common.collect.Maps.newHashMap;

public class IssuesAction implements RequestHandler {
public class IssuesAction implements BatchAction {

private static final String PARAM_KEY = "key";

Expand All @@ -57,7 +56,8 @@ public IssuesAction(DbClient dbClient, IssueIndex issueIndex) {
this.issueIndex = issueIndex;
}

void define(WebService.NewController controller) {
@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("issues")
.setDescription("Return open issues")
.setSince("5.1")
Expand Down
Expand Up @@ -22,13 +22,12 @@

import org.apache.commons.io.IOUtils;
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.batch.protocol.input.ProjectRepositories;
import org.sonar.server.plugins.MimeTypes;

public class ProjectRepositoryAction implements RequestHandler {
public class ProjectRepositoryAction implements BatchAction {

private static final String PARAM_KEY = "key";
private static final String PARAM_PROFILE = "profile";
Expand All @@ -40,7 +39,8 @@ public ProjectRepositoryAction(ProjectRepositoryLoader projectReferentialsLoader
this.projectReferentialsLoader = projectReferentialsLoader;
}

void define(WebService.NewController controller) {
@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("project")
.setDescription("Return project repository")
.setSince("4.5")
Expand Down
@@ -0,0 +1,90 @@
/*
* 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.batch;

import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.batch.protocol.input.BatchInput;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.server.plugins.MimeTypes;
import org.sonar.server.user.UserSession;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

public class UsersAction implements BatchAction {

private static final String PARAM_LOGINS = "logins";

private final UserIndex userIndex;

public UsersAction(UserIndex userIndex) {
this.userIndex = userIndex;
}

@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("users")
.setDescription("Return user details.")
.setSince("5.2")
.setInternal(true)
.setHandler(this);

action
.createParam(PARAM_LOGINS)
.setRequired(true)
.setDescription("A comma separated list of user logins")
.setExampleValue("ada.lovelace,grace.hopper");
}

@Override
public void handle(Request request, Response response) throws Exception {
UserSession.get().checkGlobalPermission(GlobalPermissions.PREVIEW_EXECUTION);
List<String> logins = request.mandatoryParamAsStrings(PARAM_LOGINS);

response.stream().setMediaType(MimeTypes.PROTOBUF);
BatchInput.User.Builder userBuilder = BatchInput.User.newBuilder();
OutputStream output = response.stream().output();
try {
for (Iterator<UserDoc> userDocIterator = userIndex.selectUsersForBatch(logins); userDocIterator.hasNext();) {
handleUser(userDocIterator.next(), userBuilder, output);
}
} finally {
output.close();
}
}

private void handleUser(UserDoc user, BatchInput.User.Builder userBuilder, OutputStream out) {
userBuilder.setLogin(user.login())
.setName(user.name());
try {
userBuilder.build().writeDelimitedTo(out);
} catch (IOException e) {
throw new IllegalStateException("Unable to serialize user", e);
}
userBuilder.clear();
}
}
Expand Up @@ -95,6 +95,7 @@
import org.sonar.server.batch.IssuesAction;
import org.sonar.server.batch.ProjectRepositoryAction;
import org.sonar.server.batch.ProjectRepositoryLoader;
import org.sonar.server.batch.UsersAction;
import org.sonar.server.charts.ChartFactory;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentService;
Expand Down Expand Up @@ -611,6 +612,7 @@ void startLevel4Components(ComponentContainer pico) {
pico.addSingleton(ProjectRepositoryLoader.class);
pico.addSingleton(SubmitReportWsAction.class);
pico.addSingleton(IssuesAction.class);
pico.addSingleton(UsersAction.class);
pico.addSingleton(BatchWs.class);

// Dashboard
Expand Down
Expand Up @@ -24,20 +24,34 @@
import org.elasticsearch.action.get.GetRequestBuilder;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequestBuilder;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolFilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.sonar.api.ServerComponent;
import org.sonar.server.es.EsClient;
import org.sonar.server.exceptions.NotFoundException;

import javax.annotation.CheckForNull;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;

public class UserIndex implements ServerComponent {

private static final int SCROLL_TIME_IN_MINUTES = 3;

private final EsClient esClient;

public UserIndex(EsClient esClient) {
Expand Down Expand Up @@ -100,4 +114,54 @@ public List<UserDoc> getAtMostThreeActiveUsersForScmAccount(String scmAccount) {
return result;
}

public Iterator<UserDoc> selectUsersForBatch(List<String> logins) {
BoolFilterBuilder filter = FilterBuilders.boolFilter()
.must(FilterBuilders.termsFilter(UserIndexDefinition.FIELD_LOGIN, logins));

SearchRequestBuilder requestBuilder = esClient
.prepareSearch(UserIndexDefinition.INDEX)
.setTypes(UserIndexDefinition.TYPE_USER)
.setSearchType(SearchType.SCAN)
.addSort(SortBuilders.fieldSort(UserIndexDefinition.FIELD_LOGIN).order(SortOrder.ASC))
.setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES))
.setSize(10000)
.setFetchSource(
new String[] {UserIndexDefinition.FIELD_LOGIN, UserIndexDefinition.FIELD_NAME},
null)
.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), filter));
SearchResponse response = requestBuilder.get();

return scroll(response.getScrollId());
}

// Scrolling within the index
private Iterator<UserDoc> scroll(final String scrollId) {
return new Iterator<UserDoc>() {
private final Queue<SearchHit> hits = new ArrayDeque<>();

@Override
public boolean hasNext() {
if (hits.isEmpty()) {
SearchScrollRequestBuilder esRequest = esClient.prepareSearchScroll(scrollId)
.setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES));
Collections.addAll(hits, esRequest.get().getHits().getHits());
}
return !hits.isEmpty();
}

@Override
public UserDoc next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return new UserDoc(hits.poll().getSource());
}

@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove item when scrolling");
}
};
}

}
Expand Up @@ -65,7 +65,7 @@ public void setUp() throws Exception {
when(dbClient.openSession(false)).thenReturn(session);
when(dbClient.metricDao()).thenReturn(metricDao);

tester = new WsTester(new BatchWs(mock(BatchIndex.class), new GlobalRepositoryAction(dbClient, propertiesDao), mock(ProjectRepositoryAction.class), mock(IssuesAction.class)));
tester = new WsTester(new BatchWs(mock(BatchIndex.class), new GlobalRepositoryAction(dbClient, propertiesDao)));
}

@Test
Expand Down
Expand Up @@ -35,15 +35,19 @@
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.DbTester;
import org.sonar.core.properties.PropertiesDao;
import org.sonar.server.component.ComponentTesting;
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.db.DbClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.issue.IssueTesting;
import org.sonar.server.issue.db.IssueDao;
import org.sonar.server.issue.index.*;
import org.sonar.server.issue.index.IssueAuthorizationDao;
import org.sonar.server.issue.index.IssueAuthorizationIndexer;
import org.sonar.server.issue.index.IssueDoc;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
import org.sonar.test.DbTests;
Expand Down Expand Up @@ -94,12 +98,7 @@ public void before() throws Exception {
issuesAction = new IssuesAction(dbClient, issueIndex);
componentDao = new ComponentDao();

tester = new WsTester(new BatchWs(
new BatchIndex(mock(Server.class)),
new GlobalRepositoryAction(mock(DbClient.class), mock(PropertiesDao.class)),
new ProjectRepositoryAction(mock(ProjectRepositoryLoader.class)),
issuesAction)
);
tester = new WsTester(new BatchWs(new BatchIndex(mock(Server.class)), issuesAction));
}

@After
Expand Down
Expand Up @@ -43,8 +43,7 @@ public class ProjectRepositoryActionTest {

@Before
public void setUp() throws Exception {
tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalRepositoryAction.class),
new ProjectRepositoryAction(projectRepositoryLoader), mock(IssuesAction.class)));
tester = new WsTester(new BatchWs(mock(BatchIndex.class), new ProjectRepositoryAction(projectRepositoryLoader)));
}

@Test
Expand Down

0 comments on commit cad73b7

Please sign in to comment.