LCOV - code coverage report
Current view: top level - server/restapi/change - QueryChanges.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 62 67 92.5 %
Date: 2022-11-19 15:00:39 Functions: 12 14 85.7 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750