All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.query.change.QueryProcessor Maven / Gradle / Ivy

// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.query.change;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.data.ChangeAttribute;
import com.google.gerrit.server.data.PatchSetAttribute;
import com.google.gerrit.server.data.QueryStatsAttribute;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gson.Gson;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

public class QueryProcessor {
  private static final Logger log =
      LoggerFactory.getLogger(QueryProcessor.class);

  private final Comparator cmpAfter =
      new Comparator() {
        @Override
        public int compare(ChangeData a, ChangeData b) {
          try {
            return a.change().getSortKey().compareTo(b.change().getSortKey());
          } catch (OrmException e) {
            return 0;
          }
        }
      };

  private final Comparator cmpBefore =
      new Comparator() {
        @Override
        public int compare(ChangeData a, ChangeData b) {
          try {
            return b.change().getSortKey().compareTo(a.change().getSortKey());
          } catch (OrmException e) {
            return 0;
          }
        }
      };

  public static enum OutputFormat {
    TEXT, JSON
  }

  private final Gson gson = new Gson();
  private final SimpleDateFormat sdf =
      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");

  private final EventFactory eventFactory;
  private final ChangeQueryBuilder queryBuilder;
  private final ChangeQueryRewriter queryRewriter;
  private final Provider db;
  private final TrackingFooters trackingFooters;
  private final CurrentUser user;
  private final int maxLimit;

  private OutputFormat outputFormat = OutputFormat.TEXT;
  private int limit;
  private int start;
  private String sortkeyAfter;
  private String sortkeyBefore;
  private boolean includePatchSets;
  private boolean includeCurrentPatchSet;
  private boolean includeApprovals;
  private boolean includeComments;
  private boolean includeFiles;
  private boolean includeCommitMessage;
  private boolean includeDependencies;
  private boolean includeSubmitRecords;
  private boolean includeAllReviewers;

  private OutputStream outputStream = DisabledOutputStream.INSTANCE;
  private PrintWriter out;
  private boolean moreResults;

  @Inject
  QueryProcessor(EventFactory eventFactory,
      ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
      ChangeQueryRewriter queryRewriter, Provider db,
      TrackingFooters trackingFooters) {
    this.eventFactory = eventFactory;
    this.queryBuilder = queryBuilder.create(currentUser);
    this.queryRewriter = queryRewriter;
    this.db = db;
    this.trackingFooters = trackingFooters;
    this.user = currentUser;
    this.maxLimit = currentUser.getCapabilities()
      .getRange(GlobalCapability.QUERY_LIMIT)
      .getMax();
    this.moreResults = false;
  }

  int getLimit() {
    return limit;
  }

  void setLimit(int n) {
    limit = n;
  }

  public void setStart(int n) {
    start = n;
  }

  void setSortkeyAfter(String sortkey) {
    sortkeyAfter = sortkey;
  }

  void setSortkeyBefore(String sortkey) {
    sortkeyBefore = sortkey;
  }

  public void setIncludePatchSets(boolean on) {
    includePatchSets = on;
  }

  public boolean getIncludePatchSets() {
    return includePatchSets;
  }

  public void setIncludeCurrentPatchSet(boolean on) {
    includeCurrentPatchSet = on;
  }

  public boolean getIncludeCurrentPatchSet() {
    return includeCurrentPatchSet;
  }

  public void setIncludeApprovals(boolean on) {
    includeApprovals = on;
  }

  public void setIncludeComments(boolean on) {
    includeComments = on;
  }

  public void setIncludeFiles(boolean on) {
    includeFiles = on;
  }

  public boolean getIncludeFiles() {
    return includeFiles;
  }

  public void setIncludeDependencies(boolean on) {
    includeDependencies = on;
  }

  public boolean getIncludeDependencies() {
    return includeDependencies;
  }

  public void setIncludeCommitMessage(boolean on) {
    includeCommitMessage = on;
  }

  public void setIncludeSubmitRecords(boolean on) {
    includeSubmitRecords = on;
  }

  public void setIncludeAllReviewers(boolean on) {
    includeAllReviewers = on;
  }

  public void setOutput(OutputStream out, OutputFormat fmt) {
    this.outputStream = out;
    this.outputFormat = fmt;
  }

  /**
   * Query for changes that match the query string.
   * 

* If a limit was specified using {@link #setLimit(int)} this method may * return up to {@code limit + 1} results, allowing the caller to determine if * there are more than {@code limit} matches and suggest to its own caller * that the query could be retried with {@link #setSortkeyBefore(String)}. */ public List queryChanges(String queryString) throws OrmException, QueryParseException { return queryChanges(ImmutableList.of(queryString)).get(0); } /** * Query for changes that match the query string. *

* If a limit was specified using {@link #setLimit(int)} this method may * return up to {@code limit + 1} results, allowing the caller to determine if * there are more than {@code limit} matches and suggest to its own caller * that the query could be retried with {@link #setSortkeyBefore(String)}. */ public List> queryChanges(List queries) throws OrmException, QueryParseException { final Predicate visibleToMe = queryBuilder.is_visible(); int cnt = queries.size(); // Parse and rewrite all queries. List limits = Lists.newArrayListWithCapacity(cnt); List sources = Lists.newArrayListWithCapacity(cnt); for (String query : queries) { Predicate q = parseQuery(query, visibleToMe); Predicate s = queryRewriter.rewrite(q, start); if (!(s instanceof ChangeDataSource)) { q = Predicate.and(queryBuilder.status_open(), q); s = queryRewriter.rewrite(q, start); } if (!(s instanceof ChangeDataSource)) { throw new QueryParseException("invalid query: " + s); } // Don't trust QueryRewriter to have left the visible predicate. AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start); limits.add(limit(q)); sources.add(a); } // Run each query asynchronously, if supported. List> matches = Lists.newArrayListWithCapacity(cnt); for (ChangeDataSource s : sources) { matches.add(s.read()); } List> out = Lists.newArrayListWithCapacity(cnt); for (int i = 0; i < cnt; i++) { List results = matches.get(i).toList(); if (sortkeyAfter != null) { Collections.sort(results, cmpAfter); } else if (sortkeyBefore != null) { Collections.sort(results, cmpBefore); } if (results.size() > maxLimit) { moreResults = true; } int limit = limits.get(i); if (limit < results.size()) { results = results.subList(0, limit); } if (sortkeyAfter != null) { Collections.reverse(results); } out.add(results); } return out; } public void query(String queryString) throws IOException { out = new PrintWriter( // new BufferedWriter( // new OutputStreamWriter(outputStream, "UTF-8"))); try { if (isDisabled()) { ErrorMessage m = new ErrorMessage(); m.message = "query disabled"; show(m); return; } try { final QueryStatsAttribute stats = new QueryStatsAttribute(); stats.runTimeMilliseconds = TimeUtil.nowMs(); List results = queryChanges(queryString); ChangeAttribute c = null; for (ChangeData d : results) { ChangeControl cc = d.changeControl().forUser(user); LabelTypes labelTypes = cc.getLabelTypes(); c = eventFactory.asChangeAttribute(d.change()); eventFactory.extend(c, d.change()); if (!trackingFooters.isEmpty()) { eventFactory.addTrackingIds(c, trackingFooters.extract(d.commitFooters())); } if (includeAllReviewers) { eventFactory.addAllReviewers(c, d.notes()); } if (includeSubmitRecords) { PatchSet.Id psId = d.change().currentPatchSetId(); PatchSet patchSet = db.get().patchSets().get(psId); List submitResult = cc.canSubmit( // db.get(), patchSet, null, false, true, true); eventFactory.addSubmitRecords(c, submitResult); } if (includeCommitMessage) { eventFactory.addCommitMessage(c, d.commitMessage()); } if (includePatchSets) { if (includeFiles) { eventFactory.addPatchSets(c, d.patches(), includeApprovals ? d.approvals().asMap() : null, includeFiles, d.change(), labelTypes); } else { eventFactory.addPatchSets(c, d.patches(), includeApprovals ? d.approvals().asMap() : null, labelTypes); } } if (includeCurrentPatchSet) { PatchSet current = d.currentPatchSet(); if (current != null) { c.currentPatchSet = eventFactory.asPatchSetAttribute(current); eventFactory.addApprovals(c.currentPatchSet, d.currentApprovals(), labelTypes); if (includeFiles) { eventFactory.addPatchSetFileNames(c.currentPatchSet, d.change(), d.currentPatchSet()); } } } if (includeComments) { eventFactory.addComments(c, d.messages()); if (includePatchSets) { for (PatchSetAttribute attribute : c.patchSets) { eventFactory.addPatchSetComments(attribute, d.comments()); } } } if (includeDependencies) { eventFactory.addDependencies(c, d.change()); } show(c); } stats.rowCount = results.size(); if (moreResults) { stats.resumeSortKey = c.sortKey; } stats.runTimeMilliseconds = TimeUtil.nowMs() - stats.runTimeMilliseconds; show(stats); } catch (OrmException err) { log.error("Cannot execute query: " + queryString, err); ErrorMessage m = new ErrorMessage(); m.message = "cannot query database"; show(m); } catch (QueryParseException e) { ErrorMessage m = new ErrorMessage(); m.message = e.getMessage(); show(m); } catch (NoSuchChangeException e) { log.error("Missing change: " + e.getMessage(), e); ErrorMessage m = new ErrorMessage(); m.message = "missing change " + e.getMessage(); show(m); } } finally { try { out.flush(); } finally { out = null; } } } boolean isDisabled() { return maxLimit <= 0; } private int limit(Predicate s) { int n = Objects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit); return limit > 0 ? Math.min(n, limit) + 1 : n + 1; } private Predicate parseQuery(String queryString, final Predicate visibleToMe) throws QueryParseException { Predicate q = queryBuilder.parse(queryString); if (queryBuilder.supportsSortKey() && !ChangeQueryBuilder.hasSortKey(q)) { if (sortkeyBefore != null) { q = Predicate.and(q, queryBuilder.sortkey_before(sortkeyBefore)); } else if (sortkeyAfter != null) { q = Predicate.and(q, queryBuilder.sortkey_after(sortkeyAfter)); } else { q = Predicate.and(q, queryBuilder.sortkey_before("z")); } } return Predicate.and(q, queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit), visibleToMe); } private void show(Object data) { switch (outputFormat) { default: case TEXT: if (data instanceof ChangeAttribute) { out.print("change "); out.print(((ChangeAttribute) data).id); out.print("\n"); showText(data, 1); } else { showText(data, 0); } out.print('\n'); break; case JSON: out.print(gson.toJson(data)); out.print('\n'); break; } } private void showText(Object data, int depth) { for (Field f : fieldsOf(data.getClass())) { Object val; try { val = f.get(data); } catch (IllegalArgumentException err) { continue; } catch (IllegalAccessException err) { continue; } if (val == null) { continue; } showField(f.getName(), val, depth); } } private String indent(int spaces) { if (spaces == 0) { return ""; } else { return String.format("%" + spaces + "s", " "); } } private void showField(String field, Object value, int depth) { final int spacesDepthRatio = 2; String indent = indent(depth * spacesDepthRatio); out.print(indent); out.print(field); out.print(':'); if (value instanceof String && ((String) value).contains("\n")) { out.print(' '); // Idention for multi-line text is // current depth indetion + length of field + length of ": " indent = indent(indent.length() + field.length() + spacesDepthRatio); out.print(((String) value).replaceAll("\n", "\n" + indent).trim()); out.print('\n'); } else if (value instanceof Long && isDateField(field)) { out.print(' '); out.print(sdf.format(new Date(((Long) value) * 1000L))); out.print('\n'); } else if (isPrimitive(value)) { out.print(' '); out.print(value); out.print('\n'); } else if (value instanceof Collection) { out.print('\n'); boolean firstElement = true; for (Object thing : ((Collection) value)) { // The name of the collection was initially printed at the beginning // of this routine. Beginning at the second sub-element, reprint // the collection name so humans can separate individual elements // with less strain and error. // if (firstElement) { firstElement = false; } else { out.print(indent); out.print(field); out.print(":\n"); } if (isPrimitive(thing)) { out.print(' '); out.print(value); out.print('\n'); } else { showText(thing, depth + 1); } } } else { out.print('\n'); showText(value, depth + 1); } } private static boolean isPrimitive(Object value) { return value instanceof String // || value instanceof Number // || value instanceof Boolean // || value instanceof Enum; } private static boolean isDateField(String name) { return "lastUpdated".equals(name) // || "grantedOn".equals(name) // || "timestamp".equals(name) // || "createdOn".equals(name); } private List fieldsOf(Class type) { List r = new ArrayList<>(); if (type.getSuperclass() != null) { r.addAll(fieldsOf(type.getSuperclass())); } r.addAll(Arrays.asList(type.getDeclaredFields())); return r; } static class ErrorMessage { public final String type = "error"; public String message; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy