]> source.dussan.org Git - sonarqube.git/blob
754e72863d4ce3ec9c7097d49026d7850226e4aa
[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.Multiset;
23 import com.google.common.collect.Ordering;
24 import java.util.List;
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.OrganizationPermission.ADMINISTER;
55 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
56 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION;
57 import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional;
58 import static org.sonar.server.ws.WsUtils.writeProtobuf;
59
60 public class SearchMembersAction implements OrganizationsWsAction {
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_LIMIT);
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(PARAM_ORGANIZATION)
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       userSession.checkMembership(organization);
108
109       UserQuery.Builder userQuery = buildUserQuery(request, organization);
110       SearchOptions searchOptions = buildSearchOptions(request);
111
112       SearchResult<UserDoc> searchResults = userIndex.search(userQuery.build(), searchOptions);
113       List<String> orderedLogins = searchResults.getDocs().stream().map(UserDoc::login).collect(MoreCollectors.toList());
114
115       List<UserDto> users = dbClient.userDao().selectByLogins(dbSession, orderedLogins).stream()
116         .sorted(Ordering.explicit(orderedLogins).onResultOf(UserDto::getLogin))
117         .collect(MoreCollectors.toList());
118
119       Multiset<String> groupCountByLogin = null;
120       if (userSession.hasPermission(ADMINISTER, organization)) {
121         groupCountByLogin = dbClient.groupMembershipDao().countGroupByLoginsAndOrganization(dbSession, orderedLogins, organization.getUuid());
122       }
123
124       Common.Paging wsPaging = buildWsPaging(request, searchResults);
125       SearchMembersWsResponse wsResponse = buildResponse(users, wsPaging, groupCountByLogin);
126
127       writeProtobuf(wsResponse, request, response);
128     }
129   }
130
131   private SearchMembersWsResponse buildResponse(List<UserDto> users, Common.Paging wsPaging, @Nullable Multiset<String> groupCountByLogin) {
132     SearchMembersWsResponse.Builder response = SearchMembersWsResponse.newBuilder();
133
134     User.Builder wsUser = User.newBuilder();
135     users.stream()
136       .map(userDto -> {
137         String login = userDto.getLogin();
138         wsUser
139           .clear()
140           .setLogin(login);
141         ofNullable(emptyToNull(userDto.getEmail())).ifPresent(text -> wsUser.setAvatar(avatarResolver.create(userDto)));
142         ofNullable(userDto.getName()).ifPresent(wsUser::setName);
143         ofNullable(groupCountByLogin).ifPresent(count -> wsUser.setGroupCount(groupCountByLogin.count(login)));
144         return wsUser;
145       })
146       .forEach(response::addUsers);
147     response.setPaging(wsPaging);
148
149     return response.build();
150   }
151
152   private static UserQuery.Builder buildUserQuery(Request request, OrganizationDto organization) {
153     UserQuery.Builder userQuery = UserQuery.builder();
154     String textQuery = request.param(Param.TEXT_QUERY);
155     checkArgument(textQuery == null || textQuery.length() >= 2, "Query length must be greater than or equal to 2");
156     userQuery.setTextQuery(textQuery);
157
158     SelectionMode selectionMode = SelectionMode.fromParam(request.mandatoryParam(Param.SELECTED));
159     if (SelectionMode.DESELECTED.equals(selectionMode)) {
160       userQuery.setExcludedOrganizationUuid(organization.getUuid());
161     } else {
162       userQuery.setOrganizationUuid(organization.getUuid());
163     }
164     return userQuery;
165   }
166
167   private static SearchOptions buildSearchOptions(Request request) {
168     int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
169     return new SearchOptions().setPage(request.mandatoryParamAsInt(Param.PAGE), pageSize);
170   }
171
172   private static Common.Paging buildWsPaging(Request request, SearchResult<UserDoc> searchResults) {
173     return Common.Paging.newBuilder()
174       .setPageIndex(request.mandatoryParamAsInt(Param.PAGE))
175       .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
176       .setTotal((int) searchResults.getTotal())
177       .build();
178   }
179
180   private OrganizationDto getOrganization(DbSession dbSession, @Nullable String organizationParam) {
181     String organizationKey = Optional.ofNullable(organizationParam)
182       .orElseGet(organizationProvider.get()::getKey);
183     return checkFoundWithOptional(
184       dbClient.organizationDao().selectByKey(dbSession, organizationKey),
185       "No organization with key '%s'", organizationKey);
186   }
187 }