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
This commit is contained in:
Zac Livingston
2016-11-13 09:08:08 -07:00
committed by James Melvin
parent 78a3037859
commit cffb2459dc
12 changed files with 172 additions and 4 deletions

View File

@@ -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`

View File

@@ -63,4 +63,5 @@ public class ChangeInfo {
public Boolean _moreChanges;
public List<ProblemInfo> problems;
public List<PluginDefinedInfo> plugins;
}

View File

@@ -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;
}

View File

@@ -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<List<CommentLinkInfo>>() {})
.toProvider(CommentLinkProvider.class)
.in(SINGLETON);
bind(new TypeLiteral<DynamicMap<ChangeQueryProcessor.ChangeAttributeFactory>>() {})
.toInstance(DynamicMap.<ChangeQueryProcessor.ChangeAttributeFactory>emptyMap());
bind(String.class)
.annotatedWith(CanonicalWebUrl.class)
.toProvider(CanonicalWebUrlProvider.class);

View File

@@ -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

View File

@@ -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);

View File

@@ -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<DependencyAttribute> neededBy;
public List<SubmitRecordAttribute> submitRecords;
public List<AccountAttribute> allReviewers;
public List<PluginDefinedInfo> plugins;
}

View File

@@ -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<ChangeData> {
public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
implements PluginDefinedAttributesFactory {
/**
* Register a ChangeAttributeFactory in a config Module like this:
*
* <p>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<ReviewDb> db;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeNotes.Factory notesFactory;
private final DynamicMap<ChangeAttributeFactory> attributeFactories;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -55,7 +71,8 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData> {
ChangeIndexRewriter rewriter,
Provider<ReviewDb> db,
ChangeControl.GenericFactory changeControlFactory,
ChangeNotes.Factory notesFactory) {
ChangeNotes.Factory notesFactory,
DynamicMap<ChangeAttributeFactory> attributeFactories) {
super(
userProvider,
metrics,
@@ -67,6 +84,7 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData> {
this.db = db;
this.changeControlFactory = changeControlFactory;
this.notesFactory = notesFactory;
this.attributeFactories = attributeFactories;
}
@Override
@@ -81,6 +99,30 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData> {
return IndexedChangeQuery.createOptions(indexConfig, start, limit, requestedFields);
}
@Override
public List<PluginDefinedInfo> create(ChangeData cd) {
List<PluginDefinedInfo> plugins = new ArrayList<>(attributeFactories.plugins().size());
for (String plugin : attributeFactories.plugins()) {
for (Provider<ChangeAttributeFactory> 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<ChangeData> enforceVisibility(Predicate<ChangeData> pred) {
return new AndChangeSource(

View File

@@ -313,6 +313,7 @@ public class OutputStreamQuery {
eventFactory.addDependencies(rw, c, d.change(), d.currentPatchSet());
}
c.plugins = queryProcessor.create(d);
return c;
}

View File

@@ -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<PluginDefinedInfo> create(ChangeData cd);
}

View File

@@ -137,13 +137,18 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
int cnt = queries.size();
List<QueryResult<ChangeData>> 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<List<ChangeInfo>> res =
json.create(options)
cjson
.lazyLoad(requireLazyLoad || containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD))
.formatQueryResults(results);
for (int n = 0; n < cnt; n++) {
List<ChangeInfo> info = res.get(n);
if (results.get(n).more()) {

View File

@@ -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")