From cffb2459dce3d66e35231e439ddec934f117541f Mon Sep 17 00:00:00 2001 From: Zac Livingston Date: Sun, 13 Nov 2016 09:08:08 -0700 Subject: [PATCH] Allow plugins to output change query attributes Create a ChangeAttributeFactory interface to allow plugins to register to provide additonal attributes to be output in a change query result. Example Usage: https://gerrit-review.googlesource.com/#/c/102650/ Change-Id: I4a09d9abd8bda09a3ecde7ca203434d6ab8ab7be --- Documentation/dev-plugins.txt | 62 +++++++++++++++++++ .../gerrit/extensions/common/ChangeInfo.java | 1 + .../extensions/common/PluginDefinedInfo.java | 19 ++++++ .../gerrit/pgm/util/BatchProgramModule.java | 3 + .../gerrit/server/change/ChangeJson.java | 8 +++ .../server/config/GerritGlobalModule.java | 3 + .../gerrit/server/data/ChangeAttribute.java | 2 + .../query/change/ChangeQueryProcessor.java | 46 +++++++++++++- .../query/change/OutputStreamQuery.java | 1 + .../PluginDefinedAttributesFactory.java | 22 +++++++ .../server/query/change/QueryChanges.java | 7 ++- .../google/gerrit/sshd/commands/Query.java | 2 +- 12 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index b6d2c53f37..04adcbd070 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt @@ -736,6 +736,68 @@ public class SshModule extends AbstractModule { } ---- +[[query_attributes]] +=== Query Attributes === + +Plugins can provide additional attributes to be returned in Gerrit queries by +implementing the ChangeAttributeFactory interface and registering it to the +ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's +'configure()' method. The new attribute(s) will be output under a "plugin" +attribute in the change query output. + +The example below shows a plugin that adds two attributes ('exampleName' and +'changeValue'), to the change query output. + +[source, java] +---- +public class Module extends AbstractModule { + @Override + protected void configure() { + bind(ChangeAttributeFactory.class) + .annotatedWith(Exports.named("example")) + .to(AttributeFactory.class); + } +} + +public class AttributeFactory implements ChangeAttributeFactory { + + public class PluginAttribute extends PluginDefinedInfo { + public String exampleName; + public String changeValue; + + public PluginAttribute(ChangeData c) { + this.exampleName = "Attribute Example"; + this.changeValue = Integer.toString(c.getId().get()); + } + } + + @Override + public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) { + return new PluginAttribute(c); + } +} +---- + +Example +---- + +ssh -p 29418 localhost gerrit query "change:1" --format json + +Output: + +{ + "url" : "http://localhost:8080/1", + "plugins" : [ + { + "name" : "myplugin-name", + "exampleName" : "Attribute Example", + "changeValue" : "1" + } + ], + ... +} +---- + [[simple-configuration]] == Simple Configuration in `gerrit.config` diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java index e13962d856..2cb83844c8 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java @@ -63,4 +63,5 @@ public class ChangeInfo { public Boolean _moreChanges; public List problems; + public List plugins; } diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java new file mode 100644 index 0000000000..e6fef0f623 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java @@ -0,0 +1,19 @@ +// Copyright (C) 2017 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.extensions.common; + +public class PluginDefinedInfo { + public String name; +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java index c86d5af0af..e62521979c 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java @@ -68,6 +68,7 @@ import com.google.gerrit.server.project.ProjectCacheImpl; import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.SectionSortCache; import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeQueryProcessor; import com.google.gerrit.server.update.BatchUpdate; import com.google.inject.Inject; import com.google.inject.Module; @@ -112,6 +113,8 @@ public class BatchProgramModule extends FactoryModule { bind(new TypeLiteral>() {}) .toProvider(CommentLinkProvider.class) .in(SINGLETON); + bind(new TypeLiteral>() {}) + .toInstance(DynamicMap.emptyMap()); bind(String.class) .annotatedWith(CanonicalWebUrl.class) .toProvider(CanonicalWebUrlProvider.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java index 190f59bd1e..1a6230c18c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java @@ -118,6 +118,7 @@ import com.google.gerrit.server.project.SubmitRuleOptions; import com.google.gerrit.server.query.QueryResult; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData.ChangedLines; +import com.google.gerrit.server.query.change.PluginDefinedAttributesFactory; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -213,6 +214,7 @@ public class ChangeJson { private boolean lazyLoad = true; private AccountLoader accountLoader; private FixInput fix; + private PluginDefinedAttributesFactory pluginDefinedAttributesFactory; @Inject ChangeJson( @@ -276,6 +278,10 @@ public class ChangeJson { return this; } + public void setPluginDefinedAttributesFactory(PluginDefinedAttributesFactory pluginsFactory) { + this.pluginDefinedAttributesFactory = pluginsFactory; + } + public ChangeInfo format(ChangeResource rsrc) throws OrmException { return format(changeDataFactory.create(db.get(), rsrc.getControl())); } @@ -516,6 +522,8 @@ public class ChangeJson { out.labels = labelsFor(ctl, cd, has(LABELS), has(DETAILED_LABELS)); out.submitted = getSubmittedOn(cd); + out.plugins = + pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null; if (out.labels != null && has(DETAILED_LABELS)) { // If limited to specific patch sets but not the current patch set, don't diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 5f70786ace..e8cd040491 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -166,6 +166,7 @@ import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.SectionSortCache; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeQueryBuilder; +import com.google.gerrit.server.query.change.ChangeQueryProcessor; import com.google.gerrit.server.query.change.ConflictsCacheImpl; import com.google.gerrit.server.ssh.SshAddressesModule; import com.google.gerrit.server.tools.ToolsCatalog; @@ -378,6 +379,8 @@ public class GerritGlobalModule extends FactoryModule { DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class); DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class); + DynamicMap.mapOf(binder(), ChangeQueryProcessor.ChangeAttributeFactory.class); + install(new GitwebConfig.LegacyModule(cfg)); bind(AnonymousUser.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java index 1a8a78830f..0467c92637 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java @@ -14,6 +14,7 @@ package com.google.gerrit.server.data; +import com.google.gerrit.extensions.common.PluginDefinedInfo; import com.google.gerrit.reviewdb.client.Change; import java.util.List; @@ -43,4 +44,5 @@ public class ChangeAttribute { public List neededBy; public List submitRecords; public List allReviewers; + public List plugins; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java index 91a37d574f..efe44fab62 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java @@ -17,6 +17,8 @@ package com.google.gerrit.server.query.change; import static com.google.common.base.Preconditions.checkState; import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_LIMIT; +import com.google.gerrit.extensions.common.PluginDefinedInfo; +import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.index.IndexConfig; @@ -32,12 +34,26 @@ import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.QueryProcessor; import com.google.inject.Inject; import com.google.inject.Provider; +import java.util.ArrayList; +import java.util.List; import java.util.Set; -public class ChangeQueryProcessor extends QueryProcessor { +public class ChangeQueryProcessor extends QueryProcessor + implements PluginDefinedAttributesFactory { + /** + * Register a ChangeAttributeFactory in a config Module like this: + * + *

bind(ChangeAttributeFactory.class) .annotatedWith(Exports.named("export-name")) + * .to(YourClass.class); + */ + public interface ChangeAttributeFactory { + PluginDefinedInfo create(ChangeData a, ChangeQueryProcessor qp, String plugin); + } + private final Provider db; private final ChangeControl.GenericFactory changeControlFactory; private final ChangeNotes.Factory notesFactory; + private final DynamicMap attributeFactories; static { // It is assumed that basic rewrites do not touch visibleto predicates. @@ -55,7 +71,8 @@ public class ChangeQueryProcessor extends QueryProcessor { ChangeIndexRewriter rewriter, Provider db, ChangeControl.GenericFactory changeControlFactory, - ChangeNotes.Factory notesFactory) { + ChangeNotes.Factory notesFactory, + DynamicMap attributeFactories) { super( userProvider, metrics, @@ -67,6 +84,7 @@ public class ChangeQueryProcessor extends QueryProcessor { this.db = db; this.changeControlFactory = changeControlFactory; this.notesFactory = notesFactory; + this.attributeFactories = attributeFactories; } @Override @@ -81,6 +99,30 @@ public class ChangeQueryProcessor extends QueryProcessor { return IndexedChangeQuery.createOptions(indexConfig, start, limit, requestedFields); } + @Override + public List create(ChangeData cd) { + List plugins = new ArrayList<>(attributeFactories.plugins().size()); + for (String plugin : attributeFactories.plugins()) { + for (Provider provider : + attributeFactories.byPlugin(plugin).values()) { + PluginDefinedInfo pda = null; + try { + pda = provider.get().create(cd, this, plugin); + } catch (RuntimeException e) { + /* Eat runtime exceptions so that queries don't fail. */ + } + if (pda != null) { + pda.name = plugin; + plugins.add(pda); + } + } + } + if (plugins.isEmpty()) { + plugins = null; + } + return plugins; + } + @Override protected Predicate enforceVisibility(Predicate pred) { return new AndChangeSource( diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java index cd98087838..0d1213290a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java @@ -313,6 +313,7 @@ public class OutputStreamQuery { eventFactory.addDependencies(rw, c, d.change(), d.currentPatchSet()); } + c.plugins = queryProcessor.create(d); return c; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java new file mode 100644 index 0000000000..a7950259a9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java @@ -0,0 +1,22 @@ +// Copyright (C) 2016 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.gerrit.extensions.common.PluginDefinedInfo; +import java.util.List; + +public interface PluginDefinedAttributesFactory { + List create(ChangeData cd); +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java index 7eccf4502a..f0ef40dc2a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java @@ -137,13 +137,18 @@ public class QueryChanges implements RestReadView { int cnt = queries.size(); List> results = imp.query(qb.parse(queries)); + boolean requireLazyLoad = containsAnyOf(options, ImmutableSet.of(DETAILED_LABELS, LABELS)) && !qb.getArgs().getSchema().hasField(ChangeField.STORED_SUBMIT_RECORD_LENIENT); + + ChangeJson cjson = json.create(options); + cjson.setPluginDefinedAttributesFactory(this.imp); List> res = - json.create(options) + cjson .lazyLoad(requireLazyLoad || containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD)) .formatQueryResults(results); + for (int n = 0; n < cnt; n++) { List info = res.get(n); if (results.get(n).more()) { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java index 1192eb5a39..2e5bf71e08 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java @@ -24,7 +24,7 @@ import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; @CommandMetaData(name = "query", description = "Query the change database") -class Query extends SshCommand { +public class Query extends SshCommand { @Inject private OutputStreamQuery processor; @Option(name = "--format", metaVar = "FMT", usage = "Output display format")