Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.change; 16 : 17 : import com.google.common.collect.Iterables; 18 : import com.google.common.flogger.FluentLogger; 19 : import com.google.gerrit.extensions.client.ListChangesOption; 20 : import com.google.gerrit.extensions.client.ListOption; 21 : import com.google.gerrit.extensions.common.ChangeInfo; 22 : import com.google.gerrit.extensions.restapi.AuthException; 23 : import com.google.gerrit.extensions.restapi.BadRequestException; 24 : import com.google.gerrit.extensions.restapi.Response; 25 : import com.google.gerrit.extensions.restapi.RestReadView; 26 : import com.google.gerrit.extensions.restapi.TopLevelResource; 27 : import com.google.gerrit.index.query.QueryParseException; 28 : import com.google.gerrit.index.query.QueryRequiresAuthException; 29 : import com.google.gerrit.index.query.QueryResult; 30 : import com.google.gerrit.server.AnonymousUser; 31 : import com.google.gerrit.server.CurrentUser; 32 : import com.google.gerrit.server.DynamicOptions; 33 : import com.google.gerrit.server.change.ChangeJson; 34 : import com.google.gerrit.server.permissions.GlobalPermission; 35 : import com.google.gerrit.server.permissions.PermissionBackend; 36 : import com.google.gerrit.server.permissions.PermissionBackendException; 37 : import com.google.gerrit.server.query.change.ChangeData; 38 : import com.google.gerrit.server.query.change.ChangeQueryBuilder; 39 : import com.google.gerrit.server.query.change.ChangeQueryProcessor; 40 : import com.google.inject.Inject; 41 : import com.google.inject.Provider; 42 : import java.util.ArrayList; 43 : import java.util.Collections; 44 : import java.util.EnumSet; 45 : import java.util.HashMap; 46 : import java.util.List; 47 : import org.kohsuke.args4j.Option; 48 : 49 : public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOptions.BeanReceiver { 50 26 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 51 : 52 : private final ChangeJson.Factory json; 53 : private final ChangeQueryBuilder qb; 54 : private final Provider<ChangeQueryProcessor> queryProcessorProvider; 55 26 : private final HashMap<String, DynamicOptions.DynamicBean> dynamicBeans = new HashMap<>(); 56 : private final Provider<CurrentUser> userProvider; 57 : private final PermissionBackend permissionBackend; 58 : private EnumSet<ListChangesOption> options; 59 : private Integer limit; 60 : private Integer start; 61 : private Boolean noLimit; 62 : private Boolean skipVisibility; 63 : 64 : @Option( 65 : name = "--query", 66 : aliases = {"-q"}, 67 : metaVar = "QUERY", 68 : usage = "Query string") 69 : private List<String> queries; 70 : 71 : @Option( 72 : name = "--limit", 73 : aliases = {"-n"}, 74 : metaVar = "CNT", 75 : usage = "Maximum number of results to return") 76 : public void setLimit(int limit) { 77 23 : this.limit = limit; 78 23 : } 79 : 80 : @Option(name = "-o", usage = "Output options per change") 81 : public void addOption(ListChangesOption o) { 82 12 : options.add(o); 83 12 : } 84 : 85 : @Option(name = "-O", usage = "Output option flags, in hex") 86 : void setOptionFlagsHex(String hex) throws BadRequestException { 87 0 : options.addAll(ListOption.fromHexString(ListChangesOption.class, hex)); 88 0 : } 89 : 90 : @Option( 91 : name = "--start", 92 : aliases = {"-S"}, 93 : metaVar = "CNT", 94 : usage = "Number of changes to skip") 95 : public void setStart(int start) { 96 22 : this.start = start; 97 22 : } 98 : 99 : @Option( 100 : name = "--no-limit", 101 : usage = "Return all results, overriding the default limit. Ignored for anonymous users.") 102 : public void setNoLimit(boolean on) { 103 22 : this.noLimit = on; 104 22 : } 105 : 106 : @Option(name = "--skip-visibility", usage = "Skip visibility check, only for administrators") 107 : public void skipVisibility(boolean on) throws AuthException, PermissionBackendException { 108 1 : if (on) { 109 1 : CurrentUser user = userProvider.get(); 110 1 : permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER); 111 : } 112 1 : skipVisibility = on; 113 1 : } 114 : 115 : @Override 116 : public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) { 117 2 : dynamicBeans.put(plugin, dynamicBean); 118 2 : } 119 : 120 : @Inject 121 : QueryChanges( 122 : ChangeJson.Factory json, 123 : ChangeQueryBuilder qb, 124 : Provider<ChangeQueryProcessor> queryProcessorProvider, 125 : Provider<CurrentUser> userProvider, 126 26 : PermissionBackend permissionBackend) { 127 26 : this.json = json; 128 26 : this.qb = qb; 129 26 : this.queryProcessorProvider = queryProcessorProvider; 130 26 : this.userProvider = userProvider; 131 26 : this.permissionBackend = permissionBackend; 132 : 133 26 : options = EnumSet.noneOf(ListChangesOption.class); 134 26 : } 135 : 136 : public void addQuery(String query) { 137 23 : if (queries == null) { 138 23 : queries = new ArrayList<>(); 139 : } 140 23 : queries.add(query); 141 23 : } 142 : 143 : public String getQuery(int i) { 144 0 : return queries.get(i); 145 : } 146 : 147 : @Override 148 : public Response<List<?>> apply(TopLevelResource rsrc) 149 : throws BadRequestException, AuthException, PermissionBackendException { 150 : List<List<ChangeInfo>> out; 151 : try { 152 26 : out = query(); 153 4 : } catch (QueryRequiresAuthException e) { 154 4 : throw new AuthException("Must be signed-in to use this operator", e); 155 8 : } catch (QueryParseException e) { 156 8 : logger.atFine().withCause(e).log("Reject change query with 400 Bad Request: %s", queries); 157 8 : throw new BadRequestException(e.getMessage(), e); 158 26 : } 159 26 : return Response.ok(out.size() == 1 ? out.get(0) : out); 160 : } 161 : 162 : private List<List<ChangeInfo>> query() 163 : throws BadRequestException, QueryParseException, PermissionBackendException { 164 26 : ChangeQueryProcessor queryProcessor = queryProcessorProvider.get(); 165 26 : if (queryProcessor.isDisabled()) { 166 0 : throw new QueryParseException("query disabled"); 167 : } 168 : 169 26 : if (limit != null) { 170 23 : queryProcessor.setUserProvidedLimit(limit); 171 : } 172 26 : if (start != null) { 173 22 : if (start < 0) { 174 4 : throw new BadRequestException("'start' parameter cannot be less than zero"); 175 : } 176 22 : queryProcessor.setStart(start); 177 : } 178 26 : if (noLimit != null && !AnonymousUser.class.isAssignableFrom(userProvider.get().getClass())) { 179 22 : queryProcessor.setNoLimit(noLimit); 180 : } 181 26 : if (skipVisibility != null) { 182 1 : queryProcessor.enforceVisibility(!skipVisibility); 183 : } 184 26 : dynamicBeans.forEach((p, b) -> queryProcessor.setDynamicBean(p, b)); 185 : 186 26 : if (queries == null || queries.isEmpty()) { 187 5 : queries = Collections.singletonList("status:open"); 188 24 : } else if (queries.size() > 10) { 189 : // Hard-code a default maximum number of queries to prevent 190 : // users from submitting too much to the server in a single call. 191 0 : throw new QueryParseException("limit of 10 queries"); 192 : } 193 : 194 26 : int cnt = queries.size(); 195 26 : List<QueryResult<ChangeData>> results = queryProcessor.query(qb.parse(queries)); 196 26 : List<List<ChangeInfo>> res = 197 26 : json.create(options, queryProcessor.getInfosFactory()).format(results); 198 26 : for (int n = 0; n < cnt; n++) { 199 26 : List<ChangeInfo> info = res.get(n); 200 26 : if (results.get(n).more() && !info.isEmpty()) { 201 6 : Iterables.getLast(info)._moreChanges = true; 202 : } 203 : } 204 26 : return res; 205 : } 206 : }