From dba4e892b1e245895fe7eb5d9d56d59f2e29ff93 Mon Sep 17 00:00:00 2001 From: Craig Chapel Date: Mon, 14 Nov 2016 09:25:17 -0700 Subject: [PATCH] Allow plugins to define 'has' search operands. Create a ChangeHasOperandFactory interface which extends a more generic ChangeOperandFactory in the ChangeQueryBuilder class along with support methods and enhancements to support defining has operands that will be called from the 'has' operator in the ChangeQueryBuilder class. Change-Id: I9014b0094a9d79c2cbcafb0dcd375fbb93a46748 --- Documentation/dev-plugins.txt | 37 +++++++++++++ .../server/config/GerritGlobalModule.java | 1 + .../query/change/ChangeQueryBuilder.java | 54 +++++++++++++++---- 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index 9c98ca1031..41c464f8be 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt @@ -684,6 +684,43 @@ public class SampleOperator } ---- +[[search_operands]] +=== Search Operands === + +Plugins can define new search operands to extend change searching. +Plugin methods implementing search operands (returning a +`Predicate`), must be defined on a class implementing +one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces +(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific +`ChangeOperandFactory` class must also be bound to the `DynamicSet` from +a module's `configure()` method in the plugin. + +The new operand, when used in a search would appear as: + operatorName:operandName_pluginName + +A sample `ChangeHasOperandFactory` class implementing, and registering, a +new `has:sample_pluginName` operand is shown below: + +==== + @Singleton + public class SampleHasOperand implements ChangeHasOperandFactory { + public static class Module extends AbstractModule { + @Override + protected void configure() { + bind(ChangeHasOperandFactory.class) + .annotatedWith(Exports.named("sample") + .to(SampleHasOperand.class); + } + } + + @Override + public Predicate create(ChangeQueryBuilder builder) + throws QueryParseException { + return new HasSamplePredicate(); + } +==== + + [[simple-configuration]] == Simple Configuration in `gerrit.config` 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 60db49e9a6..6dd0fabbba 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 @@ -376,6 +376,7 @@ public class GerritGlobalModule extends FactoryModule { DynamicSet.setOf(binder(), UploadValidationListener.class); DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class); + DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class); install(new GitwebConfig.LegacyModule(cfg)); bind(AnonymousUser.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java index e62bf48b13..3db9e95358 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java @@ -28,6 +28,7 @@ import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.errors.NotSignedInException; import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Branch; @@ -98,6 +99,27 @@ public class ChangeQueryBuilder extends QueryBuilder { extends OperatorFactory { } + /** + * Converts a operand (operator value) passed to an operator into a + * {@link Predicate}. + * + * Register a ChangeOperandFactory in a config Module like this (note, for + * an example we are using the has predicate, when other predicate plugin + * operands are created they can be registered in a similar manner): + * + * bind(ChangeHasOperandFactory.class) + * .annotatedWith(Exports.named("your has operand")) + * .to(YourClass.class); + * + */ + private interface ChangeOperandFactory { + Predicate create(ChangeQueryBuilder builder) + throws QueryParseException; + } + + public interface ChangeHasOperandFactory extends ChangeOperandFactory { + } + private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$"); private static final Pattern PAT_CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN); private static final Pattern DEF_CHANGE = Pattern.compile( @@ -166,6 +188,7 @@ public class ChangeQueryBuilder extends QueryBuilder { final Provider queryProvider; final ChangeIndexRewriter rewriter; final DynamicMap opFactories; + final DynamicMap hasOperands; final IdentifiedUser.GenericFactory userFactory; final CapabilityControl.Factory capabilityControlFactory; final ChangeControl.GenericFactory changeControlGenericFactory; @@ -199,6 +222,7 @@ public class ChangeQueryBuilder extends QueryBuilder { Provider queryProvider, ChangeIndexRewriter rewriter, DynamicMap opFactories, + DynamicMap hasOperands, IdentifiedUser.GenericFactory userFactory, Provider self, CapabilityControl.Factory capabilityControlFactory, @@ -224,11 +248,9 @@ public class ChangeQueryBuilder extends QueryBuilder { StarredChangesUtil starredChangesUtil, AccountCache accountCache, @GerritServerConfig Config cfg) { - this(db, queryProvider, rewriter, opFactories, userFactory, self, - capabilityControlFactory, changeControlGenericFactory, notesFactory, - changeDataFactory, fillArgs, commentsUtil, accountResolver, groupBackend, - allProjectsName, allUsersName, patchListCache, repoManager, - projectCache, listChildProjects, submitDryRun, conflictsCache, + this(db, queryProvider, rewriter, opFactories, hasOperands, userFactory, + self, capabilityControlFactory, changeControlGenericFactory, notesFactory, changeDataFactory, fillArgs, commentsUtil, + accountResolver, groupBackend, allProjectsName, allUsersName, patchListCache, repoManager, projectCache, listChildProjects, submitDryRun, conflictsCache, trackingFooters, indexes != null ? indexes.getSearchIndex() : null, indexConfig, listMembers, starredChangesUtil, accountCache, cfg == null ? true : cfg.getBoolean("change", "allowDrafts", true)); @@ -239,6 +261,7 @@ public class ChangeQueryBuilder extends QueryBuilder { Provider queryProvider, ChangeIndexRewriter rewriter, DynamicMap opFactories, + DynamicMap hasOperands, IdentifiedUser.GenericFactory userFactory, Provider self, CapabilityControl.Factory capabilityControlFactory, @@ -293,15 +316,16 @@ public class ChangeQueryBuilder extends QueryBuilder { this.starredChangesUtil = starredChangesUtil; this.accountCache = accountCache; this.allowsDrafts = allowsDrafts; + this.hasOperands = hasOperands; } Arguments asUser(CurrentUser otherUser) { - return new Arguments(db, queryProvider, rewriter, opFactories, userFactory, - Providers.of(otherUser), + return new Arguments(db, queryProvider, rewriter, opFactories, + hasOperands, userFactory, Providers.of(otherUser), capabilityControlFactory, changeControlGenericFactory, notesFactory, - changeDataFactory, fillArgs, commentsUtil, accountResolver, groupBackend, - allProjectsName, allUsersName, patchListCache, repoManager, - projectCache, listChildProjects, submitDryRun, + changeDataFactory, fillArgs, commentsUtil, accountResolver, + groupBackend, allProjectsName, allUsersName, patchListCache, + repoManager, projectCache, listChildProjects, submitDryRun, conflictsCache, trackingFooters, index, indexConfig, listMembers, starredChangesUtil, accountCache, allowsDrafts); } @@ -453,6 +477,16 @@ public class ChangeQueryBuilder extends QueryBuilder { if ("edit".equalsIgnoreCase(value)) { return new EditByPredicate(self()); } + + // for plugins the value will be operandName_pluginName + String[] names = value.split("_"); + if (names.length == 2) { + ChangeHasOperandFactory op = args.hasOperands.get(names[1], names[0]); + if (op != null) { + return op.create(this); + } + } + throw new IllegalArgumentException(); }