| // Copyright (C) 2012 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.sshd.commands; |
| |
| import com.google.gerrit.common.data.AccountInfo; |
| import com.google.gerrit.common.data.SubmitRecord; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.reviewdb.server.ReviewDb; |
| import com.google.gerrit.rules.PrologEnvironment; |
| import com.google.gerrit.rules.StoredValues; |
| import com.google.gerrit.server.OutputFormat; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.config.AnonymousCowardName; |
| import com.google.gerrit.server.events.AccountAttribute; |
| import com.google.gerrit.server.events.SubmitLabelAttribute; |
| import com.google.gerrit.server.events.SubmitRecordAttribute; |
| import com.google.gerrit.server.project.ChangeControl; |
| import com.google.gerrit.server.project.ProjectState; |
| import com.google.gerrit.sshd.SshCommand; |
| import com.google.gson.reflect.TypeToken; |
| import com.google.inject.Inject; |
| |
| import com.googlecode.prolog_cafe.compiler.CompileException; |
| import com.googlecode.prolog_cafe.lang.BufferingPrologControl; |
| import com.googlecode.prolog_cafe.lang.JavaObjectTerm; |
| import com.googlecode.prolog_cafe.lang.ListTerm; |
| import com.googlecode.prolog_cafe.lang.Prolog; |
| import com.googlecode.prolog_cafe.lang.PrologClassLoader; |
| import com.googlecode.prolog_cafe.lang.PrologException; |
| import com.googlecode.prolog_cafe.lang.PrologMachineCopy; |
| import com.googlecode.prolog_cafe.lang.SymbolTerm; |
| import com.googlecode.prolog_cafe.lang.Term; |
| import com.googlecode.prolog_cafe.lang.VariableTerm; |
| |
| import org.kohsuke.args4j.Argument; |
| import org.kohsuke.args4j.Option; |
| |
| import java.io.InputStreamReader; |
| import java.io.PushbackReader; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** Command that allows testing of prolog submit-rules in a live instance. */ |
| final class TestSubmitRule extends SshCommand { |
| @Inject |
| private ReviewDb db; |
| |
| @Inject |
| private PrologEnvironment.Factory envFactory; |
| |
| @Inject |
| private ChangeControl.Factory ccFactory; |
| |
| @Inject |
| private AccountCache accountCache; |
| |
| final @AnonymousCowardName String anonymousCowardName; |
| |
| @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment") |
| private String changeId; |
| |
| @Option(name = "-s", |
| usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch") |
| private boolean useStdin; |
| |
| @Option(name = "--format", metaVar = "FMT", usage = "Output display format") |
| private OutputFormat format = OutputFormat.TEXT; |
| |
| @Option(name = "--no-filters", aliases = {"-n"}, |
| usage = "Don't run the submit_filter/2 from the parent projects") |
| private boolean skipSubmitFilters; |
| |
| private static final String[] PACKAGE_LIST = {Prolog.BUILTIN, "gerrit"}; |
| |
| @Inject |
| public TestSubmitRule(@AnonymousCowardName String anonymous) { |
| anonymousCowardName = anonymous; |
| } |
| private PrologMachineCopy newMachine() { |
| BufferingPrologControl ctl = new BufferingPrologControl(); |
| ctl.setMaxDatabaseSize(16 * 1024); |
| ctl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader())); |
| return PrologMachineCopy.save(ctl); |
| } |
| |
| @Override |
| protected void run() throws UnloggedFailure { |
| PushbackReader inReader = new PushbackReader(new InputStreamReader(in)); |
| |
| try { |
| PrologEnvironment pcl; |
| |
| List<Change> changeList = |
| db.changes().byKey(new Change.Key(changeId)).toList(); |
| if (changeList.size() != 1) |
| throw new UnloggedFailure(1, "Invalid ChangeId"); |
| |
| Change c = changeList.get(0); |
| PatchSet ps = db.patchSets().get(c.currentPatchSetId()); |
| // Will throw exception if current user can not access this change, and |
| // thus will leak information that a change-id is valid even though the |
| // user are not allowed to see the change. |
| // See http://code.google.com/p/gerrit/issues/detail?id=1586 |
| ChangeControl cc = ccFactory.controlFor(c); |
| ProjectState projectState = cc.getProjectControl().getProjectState(); |
| |
| if (useStdin) { |
| pcl = envFactory.create(newMachine()); |
| } else { |
| pcl = projectState.newPrologEnvironment(); |
| } |
| |
| pcl.set(StoredValues.REVIEW_DB, db); |
| pcl.set(StoredValues.CHANGE, c); |
| pcl.set(StoredValues.PATCH_SET, ps); |
| pcl.set(StoredValues.CHANGE_CONTROL, cc); |
| if (useStdin) { |
| pcl.initialize(PACKAGE_LIST); |
| pcl.execute(Prolog.BUILTIN, "consult_stream", |
| SymbolTerm.intern("stdin"), new JavaObjectTerm(inReader)); |
| } |
| |
| List<Term> results = new ArrayList<Term>(); |
| Term submitRule = |
| pcl.once("gerrit", "locate_submit_rule", new VariableTerm()); |
| |
| for (Term[] template : pcl.all("gerrit", "can_submit", submitRule, |
| new VariableTerm())) { |
| results.add(template[1]); |
| } |
| |
| if (!skipSubmitFilters) { |
| runSubmitFilters(projectState, results, pcl); |
| } |
| |
| List<SubmitRecord> res = cc.resultsToSubmitRecord(submitRule, results); |
| for (SubmitRecord r : res) { |
| if (format.isJson()) { |
| SubmitRecordAttribute submitRecord = new SubmitRecordAttribute(); |
| submitRecord.status = r.status.name(); |
| |
| List<SubmitLabelAttribute> submitLabels = new LinkedList<SubmitLabelAttribute>(); |
| for(SubmitRecord.Label l : r.labels) { |
| SubmitLabelAttribute label = new SubmitLabelAttribute(); |
| label.label = l.label; |
| label.status= l.status.name(); |
| if(l.appliedBy != null) { |
| Account a = accountCache.get(l.appliedBy).getAccount(); |
| label.by = new AccountAttribute(); |
| label.by.email = a.getPreferredEmail(); |
| label.by.name = a.getFullName(); |
| label.by.username = a.getUserName(); |
| } |
| submitLabels.add(label); |
| } |
| submitRecord.labels = submitLabels; |
| format.newGson().toJson(submitRecord, new TypeToken<SubmitRecordAttribute>() {}.getType(), stdout); |
| stdout.print('\n'); |
| } else { |
| for(SubmitRecord.Label l : r.labels) { |
| stdout.print(l.label + ": " + l.status); |
| if(l.appliedBy != null) { |
| AccountInfo a = new AccountInfo(accountCache.get(l.appliedBy).getAccount()); |
| stdout.print(" by " + a.getNameEmail(anonymousCowardName)); |
| } |
| stdout.print('\n'); |
| } |
| stdout.print("\n" + r.status.name() + "\n"); |
| } |
| } |
| } catch (Exception e) { |
| throw new UnloggedFailure("Processing of prolog script failed: " + e); |
| } |
| } |
| |
| private void runSubmitFilters(ProjectState projectState, List<Term> results, |
| PrologEnvironment pcl) throws UnloggedFailure { |
| ProjectState parentState = projectState.getParentState(); |
| PrologEnvironment childEnv = pcl; |
| Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>(); |
| projectsSeen.add(projectState.getProject().getNameKey()); |
| |
| while (parentState != null) { |
| if (!projectsSeen.add(parentState.getProject().getNameKey())) { |
| // parent has been seen before, stop walk up inheritance tree |
| break; |
| } |
| PrologEnvironment parentEnv; |
| try { |
| parentEnv = parentState.newPrologEnvironment(); |
| } catch (CompileException err) { |
| throw new UnloggedFailure("Cannot consult rules.pl for " |
| + parentState.getProject().getName() + err); |
| } |
| |
| parentEnv.copyStoredValues(childEnv); |
| Term filterRule = |
| parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm()); |
| if (filterRule != null) { |
| try { |
| Term resultsTerm = ChangeControl.toListTerm(results); |
| results.clear(); |
| Term[] template = |
| parentEnv.once("gerrit", "filter_submit_results", filterRule, |
| resultsTerm, new VariableTerm()); |
| @SuppressWarnings("unchecked") |
| final List<? extends Term> termList = |
| ((ListTerm) template[2]).toJava(); |
| results.addAll(termList); |
| } catch (PrologException err) { |
| throw new UnloggedFailure("Exception calling " + filterRule + " of " |
| + parentState.getProject().getName() + err); |
| } catch (RuntimeException err) { |
| throw new UnloggedFailure("Exception calling " + filterRule + " of " |
| + parentState.getProject().getName() + err); |
| } |
| } |
| |
| parentState = parentState.getParentState(); |
| childEnv = parentEnv; |
| } |
| } |
| } |