--- /dev/null
+/*
+ * 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
+}
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
defineIndexAction(controller);
defineFileAction(controller);
- globalRepositoryAction.define(controller);
- projectRepositoryAction.define(controller);
- issuesAction.define(controller);
+ for (BatchAction action : actions) {
+ action.define(controller);
+ }
controller.done();
}
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;
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;
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")
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;
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";
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")
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";
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")
--- /dev/null
+/*
+ * 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();
+ }
+}
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;
pico.addSingleton(ProjectRepositoryLoader.class);
pico.addSingleton(SubmitReportWsAction.class);
pico.addSingleton(IssuesAction.class);
+ pico.addSingleton(UsersAction.class);
pico.addSingleton(BatchWs.class);
// Dashboard
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) {
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");
+ }
+ };
+ }
+
}
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
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.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;
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
@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
--- /dev/null
+/*
+ * 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.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.platform.Server;
+import org.sonar.batch.protocol.input.BatchInput.User;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
+import org.sonar.server.ws.WsTester;
+
+import java.io.ByteArrayInputStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class UsersActionTest {
+
+ @ClassRule
+ public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
+
+ UserIndex userIndex;
+
+ WsTester tester;
+
+ UsersAction usersAction;
+
+ @Before
+ public void before() throws Exception {
+ es.truncateIndices();
+
+ userIndex = new UserIndex(es.client());
+ usersAction = new UsersAction(userIndex);
+
+ tester = new WsTester(new BatchWs(new BatchIndex(mock(Server.class)), usersAction));
+ }
+
+ @Test
+ public void return_minimal_fields() throws Exception {
+ es.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER,
+ new UserDoc().setLogin("ada.lovelace").setName("Ada Lovelace").setActive(false),
+ new UserDoc().setLogin("grace.hopper").setName("Grace Hopper").setActive(true));
+
+ MockUserSession.set().setLogin("sonarqtech").setGlobalPermissions(GlobalPermissions.PREVIEW_EXECUTION);
+
+ WsTester.TestRequest request = tester.newGetRequest("batch", "users").setParam("logins", "ada.lovelace,grace.hopper");
+
+ ByteArrayInputStream input = new ByteArrayInputStream(request.execute().output());
+
+ User user = User.parseDelimitedFrom(input);
+ assertThat(user.getLogin()).isEqualTo("ada.lovelace");
+ assertThat(user.getName()).isEqualTo("Ada Lovelace");
+
+ user = User.parseDelimitedFrom(input);
+ assertThat(user.getLogin()).isEqualTo("grace.hopper");
+ assertThat(user.getName()).isEqualTo("Grace Hopper");
+
+ assertThat(User.parseDelimitedFrom(input)).isNull();
+ }
+}