]> source.dussan.org Git - sonarqube.git/blob
003e9663d3145f4fa2bc8188b363b9faf3e9ded1
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.organization.ws;
21
22 import com.google.common.collect.Ordering;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import javax.annotation.Nullable;
27 import org.sonar.api.server.ws.Change;
28 import org.sonar.api.server.ws.Request;
29 import org.sonar.api.server.ws.Response;
30 import org.sonar.api.server.ws.WebService;
31 import org.sonar.api.server.ws.WebService.Param;
32 import org.sonar.api.server.ws.WebService.SelectionMode;
33 import org.sonar.core.util.stream.MoreCollectors;
34 import org.sonar.db.DbClient;
35 import org.sonar.db.DbSession;
36 import org.sonar.db.organization.OrganizationDto;
37 import org.sonar.db.user.UserDto;
38 import org.sonar.server.es.SearchOptions;
39 import org.sonar.server.es.SearchResult;
40 import org.sonar.server.issue.AvatarResolver;
41 import org.sonar.server.organization.DefaultOrganizationProvider;
42 import org.sonar.server.user.UserSession;
43 import org.sonar.server.user.index.UserDoc;
44 import org.sonar.server.user.index.UserIndex;
45 import org.sonar.server.user.index.UserQuery;
46 import org.sonarqube.ws.Common;
47 import org.sonarqube.ws.Organizations.SearchMembersWsResponse;
48 import org.sonarqube.ws.Organizations.User;
49
50 import static com.google.common.base.Preconditions.checkArgument;
51 import static com.google.common.base.Strings.emptyToNull;
52 import static java.util.Optional.ofNullable;
53 import static org.sonar.api.server.ws.WebService.SelectionMode.SELECTED;
54 import static org.sonar.db.permission.GlobalPermission.ADMINISTER;
55 import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE;
56 import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional;
57 import static org.sonar.server.ws.WsUtils.writeProtobuf;
58
59 public class SearchMembersAction implements OrganizationsWsAction {
60   private static final String ORGANIZATION_PARAM = "organization";
61
62   private final DbClient dbClient;
63   private final UserIndex userIndex;
64   private final DefaultOrganizationProvider organizationProvider;
65   private final UserSession userSession;
66   private final AvatarResolver avatarResolver;
67
68   public SearchMembersAction(DbClient dbClient, UserIndex userIndex, DefaultOrganizationProvider organizationProvider, UserSession userSession, AvatarResolver avatarResolver) {
69     this.dbClient = dbClient;
70     this.userIndex = userIndex;
71     this.organizationProvider = organizationProvider;
72     this.userSession = userSession;
73     this.avatarResolver = avatarResolver;
74   }
75
76   @Override
77   public void define(WebService.NewController context) {
78     WebService.NewAction action = context.createAction("search_members")
79       .setDescription("Search members of an organization.<br/>" +
80         "Require organization membership.")
81       .setResponseExample(getClass().getResource("search_members-example.json"))
82       .setSince("6.4")
83       .setInternal(true)
84       .setChangelog(new Change("7.3", "This action now requires organization membership"))
85       .setHandler(this);
86
87     action.createSearchQuery("orwe", "names", "logins")
88       .setMinimumLength(2);
89     action.addPagingParams(50, MAX_PAGE_SIZE);
90
91     action.createParam(Param.SELECTED)
92       .setDescription("Depending on the value, show only selected items (selected=selected) or deselected items (selected=deselected).")
93       .setInternal(true)
94       .setDefaultValue(SELECTED.value())
95       .setPossibleValues(SELECTED.value(), SelectionMode.DESELECTED.value());
96
97     action.createParam(ORGANIZATION_PARAM)
98       .setDescription("Organization key")
99       .setInternal(true)
100       .setRequired(false);
101   }
102
103   @Override
104   public void handle(Request request, Response response) throws Exception {
105     try (DbSession dbSession = dbClient.openSession(false)) {
106       OrganizationDto organization = getOrganization(dbSession, request.param("organization"));
107
108       UserQuery.Builder userQuery = buildUserQuery(request, organization);
109       SearchOptions searchOptions = buildSearchOptions(request);
110
111       SearchResult<UserDoc> searchResults = userIndex.search(userQuery.build(), searchOptions);
112       List<String> orderedLogins = searchResults.getDocs().stream().map(UserDoc::login).collect(MoreCollectors.toList());
113
114       List<UserDto> users = dbClient.userDao().selectByLogins(dbSession, orderedLogins).stream()
115         .sorted(Ordering.explicit(orderedLogins).onResultOf(UserDto::getLogin))
116         .collect(MoreCollectors.toList());
117
118       Map<String, Integer> groupCountByLogin = null;
119       if (userSession.hasPermission(ADMINISTER)) {
120         groupCountByLogin = dbClient.groupMembershipDao().countGroupsByUsers(dbSession, orderedLogins);
121       }
122
123       Common.Paging wsPaging = buildWsPaging(request, searchResults);
124       SearchMembersWsResponse wsResponse = buildResponse(users, wsPaging, groupCountByLogin);
125
126       writeProtobuf(wsResponse, request, response);
127     }
128   }
129
130   private SearchMembersWsResponse buildResponse(List<UserDto> users, Common.Paging wsPaging, @Nullable Map<String, Integer> groupCountByLogin) {
131     SearchMembersWsResponse.Builder response = SearchMembersWsResponse.newBuilder();
132
133     User.Builder wsUser = User.newBuilder();
134     users.stream()
135       .map(userDto -> {
136         String login = userDto.getLogin();
137         wsUser
138           .clear()
139           .setLogin(login);
140         ofNullable(emptyToNull(userDto.getEmail())).ifPresent(text -> wsUser.setAvatar(avatarResolver.create(userDto)));
141         ofNullable(userDto.getName()).ifPresent(wsUser::setName);
142         ofNullable(groupCountByLogin).ifPresent(count -> wsUser.setGroupCount(groupCountByLogin.getOrDefault(login, 0)));
143         return wsUser;
144       })
145       .forEach(response::addUsers);
146     response.setPaging(wsPaging);
147
148     return response.build();
149   }
150
151   private static UserQuery.Builder buildUserQuery(Request request, OrganizationDto organization) {
152     UserQuery.Builder userQuery = UserQuery.builder();
153     String textQuery = request.param(Param.TEXT_QUERY);
154     checkArgument(textQuery == null || textQuery.length() >= 2, "Query length must be greater than or equal to 2");
155     userQuery.setTextQuery(textQuery);
156
157     SelectionMode selectionMode = SelectionMode.fromParam(request.mandatoryParam(Param.SELECTED));
158     if (SelectionMode.DESELECTED.equals(selectionMode)) {
159       userQuery.setExcludedOrganizationUuid(organization.getUuid());
160     } else {
161       userQuery.setOrganizationUuid(organization.getUuid());
162     }
163     return userQuery;
164   }
165
166   private static SearchOptions buildSearchOptions(Request request) {
167     int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
168     return new SearchOptions().setPage(request.mandatoryParamAsInt(Param.PAGE), pageSize);
169   }
170
171   private static Common.Paging buildWsPaging(Request request, SearchResult<UserDoc> searchResults) {
172     return Common.Paging.newBuilder()
173       .setPageIndex(request.mandatoryParamAsInt(Param.PAGE))
174       .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
175       .setTotal((int) searchResults.getTotal())
176       .build();
177   }
178
179   private OrganizationDto getOrganization(DbSession dbSession, @Nullable String organizationParam) {
180     String organizationKey = Optional.ofNullable(organizationParam)
181       .orElseGet(organizationProvider.get()::getKey);
182     return checkFoundWithOptional(
183       dbClient.organizationDao().selectByKey(dbSession, organizationKey),
184       "No organization with key '%s'", organizationKey);
185   }
186 }