blob: 64b1c0d76f8a8d1fd1a255bd6a7a4eb8ad201199 [file] [log] [blame]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001= Gerrit Code Review - Plugin Development
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002
Edwin Kempinaf275322012-07-16 11:04:01 +02003The Gerrit server functionality can be extended by installing plugins.
4This page describes how plugins for Gerrit can be developed.
5
6Depending on how tightly the extension code is coupled with the Gerrit
7server code, there is a distinction between `plugins` and `extensions`.
8
Edwin Kempinf5a77332012-07-18 11:17:53 +02009[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020010A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070011JVM as Gerrit. It has full access to all server internals. Plugins
12are tightly coupled to a specific major.minor server version and
13may require source code changes to compile against a different
14server version.
15
Edwin Kempinf5a77332012-07-18 11:17:53 +020016[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020017An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070018in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020019server's internals. The limited visibility reduces the extension's
20dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070021of server versions.
22
23Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070024
Edwin Kempinf878c4b2012-07-18 09:34:25 +020025[[getting-started]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080026== Getting started
Deniz Türkoglueb78b602012-05-07 14:02:36 -070027
David Ostrovskya052e522016-12-10 17:53:16 +010028To get started with the development of a plugin clone the sample
29plugin:
David Pursehousecf2e9002017-03-01 19:10:43 +090030
Dave Borowitz5cc8f662012-05-21 09:51:36 -070031----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090032$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070033----
David Pursehousecf2e9002017-03-01 19:10:43 +090034
35This is a project that demonstrates the various features of the
36plugin API. It can be taken as an example to develop an own plugin.
37
Edwin Kempinf878c4b2012-07-18 09:34:25 +020038When starting from this example one should take care to adapt the
David Ostrovskya052e522016-12-10 17:53:16 +010039`Gerrit-ApiVersion` in the `BUILD` to the version of Gerrit for which
40the plugin is developed.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070041
Edwin Kempinf878c4b2012-07-18 09:34:25 +020042[[API]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080043== API
Edwin Kempinf878c4b2012-07-18 09:34:25 +020044
45There are two different API formats offered against which plugins can
46be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070047
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070048gerrit-extension-api.jar::
49 A stable but thin interface. Suitable for extensions that need
50 to be notified of events, but do not require tight coupling to
51 the internals of Gerrit. Extensions built against this API can
52 expect to be binary compatible across a wide range of server
53 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070054
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070055gerrit-plugin-api.jar::
56 The complete internals of the Gerrit server, permitting a
57 plugin to tightly couple itself and provide additional
58 functionality that is not possible as an extension. Plugins
59 built against this API are expected to break at the source
60 code level between every major.minor Gerrit release. A plugin
61 that compiles against 2.5 will probably need source code level
62 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070063
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080064== Manifest
Deniz Türkoglueb78b602012-05-07 14:02:36 -070065
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070066Plugins may provide optional description information with standard
67manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070068
Michael Ochmannb99feab2016-07-06 14:10:22 +020069----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070070 Implementation-Title: Example plugin showing examples
71 Implementation-Version: 1.0
72 Implementation-Vendor: Example, Inc.
Michael Ochmannb99feab2016-07-06 14:10:22 +020073----
Nasser Grainawie033b262012-05-09 17:54:21 -070074
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080075=== ApiType
Nasser Grainawie033b262012-05-09 17:54:21 -070076
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070077Plugins using the tightly coupled `gerrit-plugin-api.jar` must
78declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +020079internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070080API will be assumed. This may cause ClassNotFoundExceptions when
81loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -070082
Michael Ochmannb99feab2016-07-06 14:10:22 +020083----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070084 Gerrit-ApiType: plugin
Michael Ochmannb99feab2016-07-06 14:10:22 +020085----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070086
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080087=== Explicit Registration
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070088
89Plugins that use explicit Guice registration must name the Guice
90modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +020091manifest. `Gerrit-Module` supplies bindings to the core server;
92`Gerrit-SshModule` supplies SSH commands to the SSH server (if
93enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094server (if enabled). If no modules are named automatic registration
95will be performed by scanning all classes in the plugin JAR for
96`@Listen` and `@Export("")` annotations.
97
Michael Ochmannb99feab2016-07-06 14:10:22 +020098----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070099 Gerrit-Module: tld.example.project.CoreModuleClassName
100 Gerrit-SshModule: tld.example.project.SshModuleClassName
101 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
Michael Ochmannb99feab2016-07-06 14:10:22 +0200102----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700103
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200104[[plugin_name]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800105=== Plugin Name
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200106
David Pursehoused128c892013-10-22 21:52:21 +0900107A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200108
Michael Ochmannb99feab2016-07-06 14:10:22 +0200109----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200110 Gerrit-PluginName: replication
Michael Ochmannb99feab2016-07-06 14:10:22 +0200111----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200112
113This is useful for plugins that contribute plugin-owned capabilities that
114are stored in the `project.config` file. Another use case is to be able to put
115project specific plugin configuration section in `project.config`. In this
116case it is advantageous to reserve the plugin name to access the configuration
117section in the `project.config` file.
118
119If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
120the plugin file name.
121
122If a plugin provides its own name, then that plugin cannot be deployed
123multiple times under different file names on one Gerrit site.
124
125For Maven driven plugins, the following line must be included in the pom.xml
126file:
127
128[source,xml]
129----
130<manifestEntries>
131 <Gerrit-PluginName>name</Gerrit-PluginName>
132</manifestEntries>
133----
134
David Ostrovskyfdbfcad2016-11-15 06:35:29 -0800135For Bazel driven plugins, the following line must be included in the BUILD
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200136configuration file:
137
138[source,python]
139----
David Pursehouse529ec252013-09-27 13:45:14 +0900140manifest_entries = [
141 'Gerrit-PluginName: name',
142]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200143----
144
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200145A plugin can get its own name injected at runtime:
146
147[source,java]
148----
149public class MyClass {
150
151 private final String pluginName;
152
153 @Inject
154 public MyClass(@PluginName String pluginName) {
155 this.pluginName = pluginName;
156 }
157
David Pursehoused128c892013-10-22 21:52:21 +0900158 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200159}
160----
161
David Pursehouse8ed0d922013-10-18 18:57:56 +0900162A plugin can get its canonical web URL injected at runtime:
163
164[source,java]
165----
166public class MyClass {
167
168 private final String url;
169
170 @Inject
171 public MyClass(@PluginCanonicalWebUrl String url) {
172 this.url = url;
173 }
174
175 [...]
176}
177----
178
179The URL is composed of the server's canonical web URL and the plugin's
180name, i.e. `http://review.example.com:8080/plugin-name`.
181
182The canonical web URL may be injected into any .jar plugin regardless of
183whether or not the plugin provides an HTTP servlet.
184
Edwin Kempinf7295742012-07-16 15:03:46 +0200185[[reload_method]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800186=== Reload Method
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700187
188If a plugin holds an exclusive resource that must be released before
189loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200190acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700191to be `restart`. Otherwise the preferred method of `reload` will
192be used, as it enables the server to hot-patch an updated plugin
193with no down time.
194
Michael Ochmannb99feab2016-07-06 14:10:22 +0200195----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700196 Gerrit-ReloadMode: restart
Michael Ochmannb99feab2016-07-06 14:10:22 +0200197----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700198
199In either mode ('restart' or 'reload') any plugin or extension can
200be updated without restarting the Gerrit server. The difference is
201how Gerrit handles the upgrade:
202
203restart::
204 The old plugin is completely stopped. All registrations of SSH
205 commands and HTTP servlets are removed. All registrations of any
206 extension points are removed. All registered LifecycleListeners
207 have their `stop()` method invoked in reverse order. The new
208 plugin is started, and registrations are made from the new
209 plugin. There is a brief window where neither the old nor the
210 new plugin is connected to the server. This means SSH commands
211 and HTTP servlets will return not found errors, and the plugin
212 will not be notified of events that occurred during the restart.
213
214reload::
215 The new plugin is started. Its LifecycleListeners are permitted
216 to perform their `start()` methods. All SSH and HTTP registrations
217 are atomically swapped out from the old plugin to the new plugin,
218 ensuring the server never returns a not found error. All extension
219 point listeners are atomically swapped out from the old plugin to
220 the new plugin, ensuring no events are missed (however some events
221 may still route to the old plugin if the swap wasn't complete yet).
222 The old plugin is stopped.
223
Edwin Kempinf7295742012-07-16 15:03:46 +0200224To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
225command can be used.
226
Luca Milanesio737285d2012-09-25 14:26:43 +0100227[[init_step]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800228=== Init step
Luca Milanesio737285d2012-09-25 14:26:43 +0100229
230Plugins can contribute their own "init step" during the Gerrit init
231wizard. This is useful for guiding the Gerrit administrator through
David Pursehouse659860f2013-12-16 14:50:04 +0900232the settings needed by the plugin to work properly.
Luca Milanesio737285d2012-09-25 14:26:43 +0100233
234For instance plugins to integrate Jira issues to Gerrit changes may
235contribute their own "init step" to allow configuring the Jira URL,
236credentials and possibly verify connectivity to validate them.
237
Michael Ochmannb99feab2016-07-06 14:10:22 +0200238----
Luca Milanesio737285d2012-09-25 14:26:43 +0100239 Gerrit-InitStep: tld.example.project.MyInitStep
Michael Ochmannb99feab2016-07-06 14:10:22 +0200240----
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
242MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900243and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100244and accessing / changing configuration settings using Section.Factory.
245
246In addition to the standard Gerrit init injections, plugins receive
247the @PluginName String injection containing their own plugin name.
248
Edwin Kempind4cfac12013-11-27 11:22:34 +0100249During their initialization plugins may get access to the
250`project.config` file of the `All-Projects` project and they are able
251to store configuration parameters in it. For this a plugin `InitStep`
Jiří Engelthaler3033a0a2015-02-16 09:44:32 +0100252can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
Edwin Kempind4cfac12013-11-27 11:22:34 +0100253
254[source,java]
255----
256 public class MyInitStep implements InitStep {
257 private final String pluginName;
258 private final ConsoleUI ui;
259 private final AllProjectsConfig allProjectsConfig;
260
Doug Kelly732ad202015-11-13 13:11:32 -0800261 @Inject
Edwin Kempind4cfac12013-11-27 11:22:34 +0100262 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
263 AllProjectsConfig allProjectsConfig) {
264 this.pluginName = pluginName;
265 this.ui = ui;
266 this.allProjectsConfig = allProjectsConfig;
267 }
268
269 @Override
270 public void run() throws Exception {
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100271 }
272
273 @Override
274 public void postRun() throws Exception {
Edwin Kempind4cfac12013-11-27 11:22:34 +0100275 ui.message("\n");
276 ui.header(pluginName + " Integration");
277 boolean enabled = ui.yesno(true, "By default enabled for all projects");
Adrian Görlerd1612972014-10-20 17:06:07 +0200278 Config cfg = allProjectsConfig.load().getConfig();
Edwin Kempind4cfac12013-11-27 11:22:34 +0100279 if (enabled) {
280 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
281 } else {
282 cfg.unset("plugin", pluginName, "enabled");
283 }
284 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
285 }
286 }
287----
288
Luca Milanesio737285d2012-09-25 14:26:43 +0100289Bear in mind that the Plugin's InitStep class will be loaded but
290the standard Gerrit runtime environment is not available and the plugin's
291own Guice modules were not initialized.
292This means the InitStep for a plugin is not executed in the same way that
293the plugin executes within the server, and may mean a plugin author cannot
294trivially reuse runtime code during init.
295
296For instance a plugin that wants to verify connectivity may need to statically
297call the constructor of their connection class, passing in values obtained
298from the Section.Factory rather than from an injected Config object.
299
David Pursehoused128c892013-10-22 21:52:21 +0900300Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
301the extraction of the plugins embedded in the distribution .war file into
302`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
303
304A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
305runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100306
David Pursehouse68153d72013-09-04 10:09:17 +0900307[source,java]
308----
309public class MyInitStep implements InitStep {
310 private final ConsoleUI ui;
311 private final Section.Factory sections;
312 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100313
David Pursehouse68153d72013-09-04 10:09:17 +0900314 @Inject
315 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
316 this.ui = ui;
317 this.sections = sections;
318 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100319 }
David Pursehouse68153d72013-09-04 10:09:17 +0900320
321 @Override
322 public void run() throws Exception {
323 ui.header("\nMy plugin");
324
325 Section mySection = getSection("myplugin", null);
326 mySection.string("Link name", "linkname", "MyLink");
327 }
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100328
329 @Override
330 public void postRun() throws Exception {
331 }
David Pursehouse68153d72013-09-04 10:09:17 +0900332}
333----
Luca Milanesio737285d2012-09-25 14:26:43 +0100334
Edwin Kempinf5a77332012-07-18 11:17:53 +0200335[[classpath]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800336== Classpath
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700337
338Each plugin is loaded into its own ClassLoader, isolating plugins
339from each other. A plugin or extension inherits the Java runtime
340and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
341from the hosting server.
342
343Plugins are loaded from a single JAR file. If a plugin needs
344additional libraries, it must include those dependencies within
345its own JAR. Plugins built using Maven may be able to use the
346link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
347to package additional dependencies. Relocating (or renaming) classes
348should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700349
Edwin Kempin98202662013-09-18 16:03:03 +0200350[[events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800351== Listening to Events
Edwin Kempin98202662013-09-18 16:03:03 +0200352
353Certain operations in Gerrit trigger events. Plugins may receive
354notifications of these events by implementing the corresponding
355listeners.
356
Martin Fick4c72aea2014-12-10 14:58:12 -0700357* `com.google.gerrit.common.EventListener`:
Edwin Kempin64059f52013-10-31 13:49:25 +0100358+
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500359Allows to listen to events without user visibility restrictions. These
360are the same link:cmd-stream-events.html#events[events] that are also streamed by
Edwin Kempin64059f52013-10-31 13:49:25 +0100361the link:cmd-stream-events.html[gerrit stream-events] command.
362
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500363* `com.google.gerrit.common.UserScopedEventListener`:
364+
365Allows to listen to events visible to the specified user. These are the
366same link:cmd-stream-events.html#events[events] that are also streamed
367by the link:cmd-stream-events.html[gerrit stream-events] command.
368
Edwin Kempin98202662013-09-18 16:03:03 +0200369* `com.google.gerrit.extensions.events.LifecycleListener`:
370+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100371Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200372
373* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
374+
375Project creation
376
377* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
378+
379Project deletion
380
Edwin Kempinb27c9392013-11-19 13:12:43 +0100381* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
382+
383Update of HEAD on a project
384
Stefan Lay310d77d2014-05-28 13:45:25 +0200385* `com.google.gerrit.extensions.events.UsageDataPublishedListener`:
386+
387Publication of usage data
388
Adrian Görlerf4a4c9a2014-08-22 17:09:18 +0200389* `com.google.gerrit.extensions.events.GarbageCollectorListener`:
390+
391Garbage collection ran on a project
392
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500393* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
394+
Hugo Arès682171f2017-04-24 13:44:43 +0200395Update of the change secondary index
396
397* `com.google.gerrit.server.extensions.events.AccountIndexedListener`:
398+
399Update of the account secondary index
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500400
Luca Milanesio45da6182016-05-12 11:33:30 +0100401* `com.google.gerrit.httpd.WebLoginListener`:
402+
403User login or logout interactively on the Web user interface.
404
405The event listener is under the Gerrit http package to automatically
406inherit the javax.servlet.http dependencies and allowing to influence
407the login or logout flow with additional redirections.
408
Yang Zhenhui2659d422013-07-30 16:59:58 +0800409[[stream-events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800410== Sending Events to the Events Stream
Yang Zhenhui2659d422013-07-30 16:59:58 +0800411
412Plugins may send events to the events stream where consumers of
413Gerrit's `stream-events` ssh command will receive them.
414
415To send an event, the plugin must invoke one of the `postEvent`
David Pursehousea9bf4762016-07-08 09:34:35 +0900416methods in the `EventDispatcher` interface, passing an instance of
Martin Fick4c72aea2014-12-10 14:58:12 -0700417its own custom event class derived from
418`com.google.gerrit.server.events.Event`.
Yang Zhenhui2659d422013-07-30 16:59:58 +0800419
David Pursehousea9bf4762016-07-08 09:34:35 +0900420[source,java]
421----
422import com.google.gerrit.common.EventDispatcher;
423import com.google.gerrit.extensions.registration.DynamicItem;
424import com.google.gwtorm.server.OrmException;
425import com.google.inject.Inject;
426
427class MyPlugin {
428 private final DynamicItem<EventDispatcher> eventDispatcher;
429
430 @Inject
431 myPlugin(DynamicItem<EventDispatcher> eventDispatcher) {
432 this.eventDispatcher = eventDispatcher;
433 }
434
435 private void postEvent(MyPluginEvent event) {
436 try {
437 eventDispatcher.get().postEvent(event);
438 } catch (OrmException e) {
439 // error handling
440 }
441 }
442}
443----
444
Martin Fick0aef6f12014-12-11 16:54:21 -0700445Plugins which define new Events should register them via the
446`com.google.gerrit.server.events.EventTypes.registerClass()`
447method. This will make the EventType known to the system.
David Pursehousea61ee502016-09-06 16:27:09 +0900448Deserializing events with the
Martin Fickf70c20a2014-12-11 17:03:15 -0700449`com.google.gerrit.server.events.EventDeserializer` class requires
450that the event be registered in EventTypes.
Martin Fick0aef6f12014-12-11 16:54:21 -0700451
Martin Fickecafc932014-12-15 14:09:41 -0700452== Modifying the Stream Event Flow
453
454It is possible to modify the stream event flow from plugins by registering
455an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
456a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
457a DynamicItem, so Gerrit may only have one copy.
458
Edwin Kempin32737602014-01-23 09:04:58 +0100459[[validation]]
David Pursehouse91c5f5e2014-01-23 18:57:33 +0900460== Validation Listeners
Edwin Kempin32737602014-01-23 09:04:58 +0100461
462Certain operations in Gerrit can be validated by plugins by
463implementing the corresponding link:config-validation.html[listeners].
464
Andrii Shyshkalov6fdc8eb2016-11-29 17:45:01 +0100465[[change-message-modifier]]
466== Change Message Modifier
467
468`com.google.gerrit.server.git.ChangeMessageModifier`:
469plugins implementing this can modify commit message of the change being
470submitted by Rebase Always and Cherry Pick submit strategies as well as
471change being queried with COMMIT_FOOTERS option.
472
Saša Živkovec85a072014-01-28 10:08:25 +0100473[[receive-pack]]
474== Receive Pack Initializers
475
476Plugins may provide ReceivePack initializers which will be invoked
477by Gerrit just before a ReceivePack instance will be used. Usually,
478plugins will make use of the setXXX methods on the ReceivePack to
479set additional properties on it.
480
Saša Živkov626c7312014-02-24 17:15:08 +0100481[[post-receive-hook]]
482== Post Receive-Pack Hooks
483
484Plugins may register PostReceiveHook instances in order to get
485notified when JGit successfully receives a pack. This may be useful
486for those plugins which would like to monitor changes in Git
487repositories.
488
Hugo Arès572d5422014-06-17 14:22:03 -0400489[[pre-upload-hook]]
490== Pre Upload-Pack Hooks
491
492Plugins may register PreUploadHook instances in order to get
493notified when JGit is about to upload a pack. This may be useful
494for those plugins which would like to monitor usage in Git
495repositories.
496
Hugo Arès41b4c0d2016-08-02 15:26:57 -0400497[[post-upload-hook]]
498== Post Upload-Pack Hooks
499
500Plugins may register PostUploadHook instances in order to get notified after
501JGit is done uploading a pack.
502
Edwin Kempinf5a77332012-07-18 11:17:53 +0200503[[ssh]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800504== SSH Commands
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700505
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700506Plugins may provide commands that can be accessed through the SSH
507interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700508
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700509Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700510
David Pursehouse68153d72013-09-04 10:09:17 +0900511[source,java]
512----
513import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100514import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700515
Ian Bulle1a12202014-02-16 17:15:42 -0800516@CommandMetaData(name="print", description="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900517class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800518 @Override
519 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900520 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700521 }
David Pursehouse68153d72013-09-04 10:09:17 +0900522}
523----
Nasser Grainawie033b262012-05-09 17:54:21 -0700524
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700525If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200526use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700527
David Pursehouse68153d72013-09-04 10:09:17 +0900528[source,java]
529----
530import com.google.gerrit.extensions.annotations.Export;
531import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700532
David Pursehouse68153d72013-09-04 10:09:17 +0900533@Export("print")
534class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800535 @Override
536 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900537 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700538 }
David Pursehouse68153d72013-09-04 10:09:17 +0900539}
540----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700541
542If explicit registration is being used, a Guice module must be
543supplied to register the SSH command and declared in the manifest
544with the `Gerrit-SshModule` attribute:
545
David Pursehouse68153d72013-09-04 10:09:17 +0900546[source,java]
547----
548import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700549
David Pursehouse68153d72013-09-04 10:09:17 +0900550class MyCommands extends PluginCommandModule {
Ian Bulle1a12202014-02-16 17:15:42 -0800551 @Override
David Pursehouse68153d72013-09-04 10:09:17 +0900552 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100553 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700554 }
David Pursehouse68153d72013-09-04 10:09:17 +0900555}
556----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700557
558For a plugin installed as name `helloworld`, the command implemented
559by PrintHello class will be available to users as:
560
561----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600562$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700563----
564
David Ostrovsky79c4d892014-03-15 13:52:46 +0100565[[multiple-commands]]
566=== Multiple Commands bound to one implementation
567
David Ostrovskye3172b32013-10-13 14:19:13 +0200568Multiple SSH commands can be bound to the same implementation class. For
569example a Gerrit Shell plugin can bind different shell commands to the same
570implementation class:
571
572[source,java]
573----
574public class SshShellModule extends PluginCommandModule {
575 @Override
576 protected void configureCommands() {
577 command("ls").to(ShellCommand.class);
578 command("ps").to(ShellCommand.class);
579 [...]
580 }
581}
582----
583
584With the possible implementation:
585
586[source,java]
587----
588public class ShellCommand extends SshCommand {
589 @Override
590 protected void run() throws UnloggedFailure {
591 String cmd = getName().substring(getPluginName().length() + 1);
592 ProcessBuilder proc = new ProcessBuilder(cmd);
593 Process cmd = proc.start();
594 [...]
595 }
596}
597----
598
599And the call:
600
601----
602$ ssh -p 29418 review.example.com shell ls
603$ ssh -p 29418 review.example.com shell ps
604----
605
David Ostrovsky79c4d892014-03-15 13:52:46 +0100606[[root-level-commands]]
607=== Root Level Commands
608
David Ostrovskyb7d97752013-11-09 05:23:26 +0100609Single command plugins are also supported. In this scenario plugin binds
610SSH command to its own name. `SshModule` must inherit from
611`SingleCommandPluginModule` class:
612
613[source,java]
614----
615public class SshModule extends SingleCommandPluginModule {
616 @Override
617 protected void configure(LinkedBindingBuilder<Command> b) {
618 b.to(ShellCommand.class);
619 }
620}
621----
622
623If the plugin above is deployed under sh.jar file in `$site/plugins`
David Pursehouse659860f2013-12-16 14:50:04 +0900624directory, generic commands can be called without specifying the
David Ostrovskyb7d97752013-11-09 05:23:26 +0100625actual SSH command. Note in the example below, that the called commands
626`ls` and `ps` was not explicitly bound:
627
628----
629$ ssh -p 29418 review.example.com sh ls
630$ ssh -p 29418 review.example.com sh ps
631----
632
Martin Fick5f6222912015-11-12 14:52:50 -0700633[[search_operators]]
Edwin Kempin4b479772016-11-14 14:34:33 -0800634== Search Operators
Martin Fick5f6222912015-11-12 14:52:50 -0700635
636Plugins can define new search operators to extend change searching by
637implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
638and registering it to an operator name in the plugin module's
639`configure()` method. The search operator name is defined during
640registration via the DynamicMap annotation mechanism. The plugin
641name will get appended to the annotated name, with an underscore
642in between, leading to the final operator name. An example
643registration looks like this:
644
645 bind(ChangeOperatorFactory.class)
646 .annotatedWith(Exports.named("sample"))
647 .to(SampleOperator.class);
648
649If this is registered in the `myplugin` plugin, then the resulting
650operator will be named `sample_myplugin`.
651
652The search operator itself is implemented by ensuring that the
653`create()` method of the class implementing the
654`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
655`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900656definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700657
658[source,java]
659----
660@Singleton
661public class SampleOperator
662 implements ChangeQueryBuilder.ChangeOperatorFactory {
Edwin Kempincc82b242016-06-28 10:00:53 +0200663 public static class MyPredicate extends OperatorChangePredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700664 ...
665 }
666
667 @Override
668 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
669 throws QueryParseException {
670 return new MyPredicate(value);
671 }
672}
673----
674
Craig Chapeldba4e892016-11-14 09:25:17 -0700675[[search_operands]]
676=== Search Operands ===
677
678Plugins can define new search operands to extend change searching.
679Plugin methods implementing search operands (returning a
680`Predicate<ChangeData>`), must be defined on a class implementing
681one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
682(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific
683`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
684a module's `configure()` method in the plugin.
685
686The new operand, when used in a search would appear as:
687 operatorName:operandName_pluginName
688
689A sample `ChangeHasOperandFactory` class implementing, and registering, a
690new `has:sample_pluginName` operand is shown below:
691
692====
693 @Singleton
694 public class SampleHasOperand implements ChangeHasOperandFactory {
695 public static class Module extends AbstractModule {
696 @Override
697 protected void configure() {
698 bind(ChangeHasOperandFactory.class)
699 .annotatedWith(Exports.named("sample")
700 .to(SampleHasOperand.class);
701 }
702 }
703
704 @Override
705 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
706 throws QueryParseException {
707 return new HasSamplePredicate();
708 }
709====
710
Zac Livingston4f083a82016-05-20 12:38:43 -0600711[[command_options]]
712=== Command Options ===
713
714Plugins can provide additional options for each of the gerrit ssh and the
715REST API commands by implementing the DynamicBean interface and registering
716it to a command class name in the plugin module's `configure()` method. The
717plugin's name will be prepended to the name of each @Option annotation found
718on the DynamicBean object provided by the plugin. The example below shows a
719plugin that adds an option to log a value from the gerrit 'ban-commits'
720ssh command.
721
722[source, java]
723----
724public class SshModule extends AbstractModule {
725 private static final Logger log = LoggerFactory.getLogger(SshModule.class);
726
727 @Override
728 protected void configure() {
729 bind(DynamicOptions.DynamicBean.class)
730 .annotatedWith(Exports.named(
731 com.google.gerrit.sshd.commands.BanCommitCommand.class))
732 .to(BanOptions.class);
733 }
734
735 public static class BanOptions implements DynamicOptions.DynamicBean {
736 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
737 private void parse(String arg) {
738 log.error("Say Hello in the Log " + arg);
739 }
740 }
741----
Craig Chapeldba4e892016-11-14 09:25:17 -0700742
Zac Livingstoncffb24592016-11-13 09:08:08 -0700743[[query_attributes]]
744=== Query Attributes ===
745
746Plugins can provide additional attributes to be returned in Gerrit queries by
747implementing the ChangeAttributeFactory interface and registering it to the
748ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's
749'configure()' method. The new attribute(s) will be output under a "plugin"
750attribute in the change query output.
751
752The example below shows a plugin that adds two attributes ('exampleName' and
753'changeValue'), to the change query output.
754
755[source, java]
756----
757public class Module extends AbstractModule {
758 @Override
759 protected void configure() {
760 bind(ChangeAttributeFactory.class)
761 .annotatedWith(Exports.named("example"))
762 .to(AttributeFactory.class);
763 }
764}
765
766public class AttributeFactory implements ChangeAttributeFactory {
767
768 public class PluginAttribute extends PluginDefinedInfo {
769 public String exampleName;
770 public String changeValue;
771
772 public PluginAttribute(ChangeData c) {
773 this.exampleName = "Attribute Example";
774 this.changeValue = Integer.toString(c.getId().get());
775 }
776 }
777
778 @Override
779 public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) {
780 return new PluginAttribute(c);
781 }
782}
783----
784
785Example
786----
787
788ssh -p 29418 localhost gerrit query "change:1" --format json
789
790Output:
791
792{
793 "url" : "http://localhost:8080/1",
794 "plugins" : [
795 {
796 "name" : "myplugin-name",
797 "exampleName" : "Attribute Example",
798 "changeValue" : "1"
799 }
800 ],
801 ...
802}
803----
804
Edwin Kempin78ca0942013-10-30 11:24:06 +0100805[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800806== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200807
808In Gerrit, global configuration is stored in the `gerrit.config` file.
809If a plugin needs global configuration, this configuration should be
810stored in a `plugin` subsection in the `gerrit.config` file.
811
Edwin Kempinc9b68602013-10-30 09:32:43 +0100812This approach of storing the plugin configuration is only suitable for
813plugins that have a simple configuration that only consists of
814key-value pairs. With this approach it is not possible to have
815subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100816configuration need to store their configuration in their
817link:#configuration[own configuration file] where they can make use of
818subsections. On the other hand storing the plugin configuration in a
819'plugin' subsection in the `gerrit.config` file has the advantage that
820administrators have all configuration parameters in one file, instead
821of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100822
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200823To avoid conflicts with other plugins, it is recommended that plugins
824only use the `plugin` subsection with their own name. For example the
825`helloworld` plugin should store its configuration in the
826`plugin.helloworld` subsection:
827
828----
829[plugin "helloworld"]
830 language = Latin
831----
832
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200833Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200834plugin can easily access its configuration and there is no need for a
835plugin to parse the `gerrit.config` file on its own:
836
837[source,java]
838----
David Pursehouse529ec252013-09-27 13:45:14 +0900839@Inject
840private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200841
David Pursehoused128c892013-10-22 21:52:21 +0900842[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200843
Edwin Kempin122622d2013-10-29 16:45:44 +0100844String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900845 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200846----
847
Edwin Kempin78ca0942013-10-30 11:24:06 +0100848[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800849== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +0100850
851Plugins can store their configuration in an own configuration file.
852This makes sense if the plugin configuration is rather complex and
853requires the usage of subsections. Plugins that have a simple
854key-value pair configuration can store their configuration in a
855link:#simple-configuration[`plugin` subsection of the `gerrit.config`
856file].
857
858The plugin configuration file must be named after the plugin and must
859be located in the `etc` folder of the review site. For example a
860configuration file for a `default-reviewer` plugin could look like
861this:
862
863.$site_path/etc/default-reviewer.config
864----
865[branch "refs/heads/master"]
866 reviewer = Project Owners
867 reviewer = john.doe@example.com
868[match "file:^.*\.txt"]
869 reviewer = My Info Developers
870----
871
David Pursehouse5b47bc42016-07-22 11:00:25 +0900872Plugins that have sensitive configuration settings can store those settings in
873an own secure configuration file. The plugin's secure configuration file must be
874named after the plugin and must be located in the `etc` folder of the review
875site. For example a secure configuration file for a `default-reviewer` plugin
876could look like this:
877
878.$site_path/etc/default-reviewer.secure.config
879----
880[auth]
881 password = secret
882----
883
Edwin Kempin78ca0942013-10-30 11:24:06 +0100884Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
885plugin can easily access its configuration:
886
887[source,java]
888----
889@Inject
890private com.google.gerrit.server.config.PluginConfigFactory cfg;
891
892[...]
893
894String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
895 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +0900896String password = cfg.getGlobalPluginConfig("default-reviewer")
897 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +0100898----
899
Edwin Kempin78ca0942013-10-30 11:24:06 +0100900
Edwin Kempin705f2842013-10-30 14:25:31 +0100901[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800902== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200903
904In Gerrit, project specific configuration is stored in the project's
905`project.config` file on the `refs/meta/config` branch. If a plugin
906needs configuration on project level (e.g. to enable its functionality
907only for certain projects), this configuration should be stored in a
908`plugin` subsection in the project's `project.config` file.
909
Edwin Kempinc9b68602013-10-30 09:32:43 +0100910This approach of storing the plugin configuration is only suitable for
911plugins that have a simple configuration that only consists of
912key-value pairs. With this approach it is not possible to have
913subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100914configuration need to store their configuration in their
915link:#project-specific-configuration[own configuration file] where they
916can make use of subsections. On the other hand storing the plugin
917configuration in a 'plugin' subsection in the `project.config` file has
918the advantage that project owners have all configuration parameters in
919one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100920
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200921To avoid conflicts with other plugins, it is recommended that plugins
922only use the `plugin` subsection with their own name. For example the
923`helloworld` plugin should store its configuration in the
924`plugin.helloworld` subsection:
925
926----
927 [plugin "helloworld"]
928 enabled = true
929----
930
931Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
932plugin can easily access its project specific configuration and there
933is no need for a plugin to parse the `project.config` file on its own:
934
935[source,java]
936----
David Pursehouse529ec252013-09-27 13:45:14 +0900937@Inject
938private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200939
David Pursehoused128c892013-10-22 21:52:21 +0900940[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200941
Edwin Kempin122622d2013-10-29 16:45:44 +0100942boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900943 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200944----
945
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200946It is also possible to get missing configuration parameters inherited
947from the parent projects:
948
949[source,java]
950----
David Pursehouse529ec252013-09-27 13:45:14 +0900951@Inject
952private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200953
David Pursehoused128c892013-10-22 21:52:21 +0900954[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200955
Edwin Kempin122622d2013-10-29 16:45:44 +0100956boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900957 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200958----
959
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200960Project owners can edit the project configuration by fetching the
961`refs/meta/config` branch, editing the `project.config` file and
962pushing the commit back.
963
Edwin Kempin9ce4f552013-11-15 16:00:00 +0100964Plugin configuration values that are stored in the `project.config`
965file can be exposed in the ProjectInfoScreen to allow project owners
966to see and edit them from the UI.
967
968For this an instance of `ProjectConfigEntry` needs to be bound for each
969parameter. The export name must be a valid Git variable name. The
970variable name is case-insensitive, allows only alphanumeric characters
971and '-', and must start with an alphabetic character.
972
Edwin Kempina6c1c452013-11-28 16:55:22 +0100973The example below shows how the parameters `plugin.helloworld.enabled`
974and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +0900975Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +0100976is provided as display name and the default value is set to `true`.
977For the parameter `plugin.helloworld.language` "Preferred Language"
978is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +0100979
980[source,java]
981----
982class Module extends AbstractModule {
983 @Override
984 protected void configure() {
985 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +0100986 .annotatedWith(Exports.named("enabled"))
987 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
988 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +0100989 .annotatedWith(Exports.named("language"))
990 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
991 }
992}
993----
994
Edwin Kempinb64d3972013-11-17 18:55:48 +0100995By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
996can be notified when this configuration parameter is updated on a
997project.
998
Janice Agustine5a9d012015-08-24 09:05:56 -0400999[[configuring-groups]]
1000=== Referencing groups in `project.config`
1001
1002Plugins can refer to groups so that when they are renamed, the project
1003config will also be updated in this section. The proper format to use is
1004the string representation of a GroupReference, as shown below.
1005
1006----
1007Group[group_name / group_uuid]
1008----
1009
Edwin Kempin705f2842013-10-30 14:25:31 +01001010[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001011== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001012
1013Plugins can store their project specific configuration in an own
1014configuration file in the projects `refs/meta/config` branch.
1015This makes sense if the plugins project specific configuration is
1016rather complex and requires the usage of subsections. Plugins that
1017have a simple key-value pair configuration can store their project
1018specific configuration in a link:#simple-project-specific-configuration[
1019`plugin` subsection of the `project.config` file].
1020
1021The plugin configuration file in the `refs/meta/config` branch must be
1022named after the plugin. For example a configuration file for a
1023`default-reviewer` plugin could look like this:
1024
1025.default-reviewer.config
1026----
1027[branch "refs/heads/master"]
1028 reviewer = Project Owners
1029 reviewer = john.doe@example.com
1030[match "file:^.*\.txt"]
1031 reviewer = My Info Developers
1032----
1033
1034Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1035plugin can easily access its project specific configuration:
1036
1037[source,java]
1038----
1039@Inject
1040private com.google.gerrit.server.config.PluginConfigFactory cfg;
1041
1042[...]
1043
1044String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1045 .getStringList("branch", "refs/heads/master", "reviewer");
1046----
1047
Edwin Kempin762da382013-10-30 14:50:01 +01001048It is also possible to get missing configuration parameters inherited
1049from the parent projects:
1050
1051[source,java]
1052----
1053@Inject
1054private com.google.gerrit.server.config.PluginConfigFactory cfg;
1055
1056[...]
1057
David Ostrovsky468e4c32014-03-22 06:05:35 -07001058String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001059 .getStringList("branch", "refs/heads/master", "reviewer");
1060----
1061
Edwin Kempin705f2842013-10-30 14:25:31 +01001062Project owners can edit the project configuration by fetching the
1063`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1064pushing the commit back.
1065
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001066== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001067
1068If a plugin wants to react on changes in the project configuration, it
1069can implement a `GitReferenceUpdatedListener` and filter on events for
1070the `refs/meta/config` branch:
1071
1072[source,java]
1073----
1074public class MyListener implements GitReferenceUpdatedListener {
1075
1076 private final MetaDataUpdate.Server metaDataUpdateFactory;
1077
1078 @Inject
1079 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1080 this.metaDataUpdateFactory = metaDataUpdateFactory;
1081 }
1082
1083 @Override
1084 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001085 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001086 Project.NameKey p = new Project.NameKey(event.getProjectName());
1087 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001088 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1089 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001090
Edwin Kempina951ba52014-01-03 14:07:28 +01001091 if (oldCfg != null && newCfg != null
1092 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001093 // submit type has changed
1094 ...
1095 }
1096 } catch (IOException | ConfigInvalidException e) {
1097 ...
1098 }
1099 }
1100 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001101
1102 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1103 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1104 ObjectId id = ObjectId.fromString(idStr);
1105 if (ObjectId.zeroId().equals(id)) {
1106 return null;
1107 }
1108 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1109 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001110}
1111----
1112
1113
David Ostrovsky7066cc02013-06-15 14:46:23 +02001114[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001115== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001116
1117Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001118commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001119
1120Plugins define the capabilities by overriding the `CapabilityDefinition`
1121abstract class:
1122
David Pursehouse68153d72013-09-04 10:09:17 +09001123[source,java]
1124----
1125public class PrintHelloCapability extends CapabilityDefinition {
1126 @Override
1127 public String getDescription() {
1128 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001129 }
David Pursehouse68153d72013-09-04 10:09:17 +09001130}
1131----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001132
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001133If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001134use auto-registration by providing an `@Export` annotation:
1135
David Pursehouse68153d72013-09-04 10:09:17 +09001136[source,java]
1137----
1138@Export("printHello")
1139public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +09001140 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001141}
1142----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001143
1144Otherwise the capability must be bound in a plugin module:
1145
David Pursehouse68153d72013-09-04 10:09:17 +09001146[source,java]
1147----
1148public class HelloWorldModule extends AbstractModule {
1149 @Override
1150 protected void configure() {
1151 bind(CapabilityDefinition.class)
1152 .annotatedWith(Exports.named("printHello"))
1153 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001154 }
David Pursehouse68153d72013-09-04 10:09:17 +09001155}
1156----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001157
1158With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001159usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001160this capability in the usual way, using the `RequiresCapability` annotation:
1161
David Pursehouse68153d72013-09-04 10:09:17 +09001162[source,java]
1163----
1164@RequiresCapability("printHello")
1165@CommandMetaData(name="print", description="Print greeting in different languages")
1166public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001167 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001168}
1169----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001170
David Ostrovskyf86bae52013-09-01 09:10:39 +02001171Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001172
David Pursehouse68153d72013-09-04 10:09:17 +09001173[source,java]
1174----
1175@RequiresCapability("printHello")
1176public class SayHelloAction extends UiAction<RevisionResource>
1177 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001178 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001179}
1180----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001181
1182Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001183capabilities and core capabilities. Per default the scope of the
1184`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1185
David Ostrovsky7066cc02013-06-15 14:46:23 +02001186* when `@RequiresCapability` is used within a plugin the scope of the
1187capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001188
David Ostrovsky7066cc02013-06-15 14:46:23 +02001189* If `@RequiresCapability` is used within the core Gerrit Code Review server
1190(and thus is outside of a plugin) the scope is the core server and will use
1191the `GlobalCapability` known to Gerrit Code Review server.
1192
1193If a plugin needs to use a core capability name (e.g. "administrateServer")
1194this can be specified by setting `scope = CapabilityScope.CORE`:
1195
David Pursehouse68153d72013-09-04 10:09:17 +09001196[source,java]
1197----
1198@RequiresCapability(value = "administrateServer", scope =
1199 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001200 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001201----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001202
David Ostrovskyf86bae52013-09-01 09:10:39 +02001203[[ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001204== UI Extension
David Ostrovskyf86bae52013-09-01 09:10:39 +02001205
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001206[[panels]]
1207=== Panels
1208
1209GWT plugins can contribute panels to Gerrit screens.
1210
1211Gerrit screens define extension points where plugins can add GWT
1212panels with custom controls:
1213
1214* Change Screen:
Edwin Kempin2a8c5152015-07-08 14:28:57 +02001215** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
1216+
1217Panel will be shown in the header bar to the right of the change
1218status.
1219
Edwin Kempin745021e2015-07-09 13:09:44 +02001220** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
1221+
1222Panel will be shown in the header bar on the right side of the buttons.
1223
Edwin Kempincbc95252015-07-09 11:37:53 +02001224** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
1225+
1226Panel will be shown in the header bar on the right side of the pop down
1227buttons.
1228
Khai Do675afc02016-07-28 16:30:37 -07001229** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
1230+
1231Panel will be shown below the commit info block.
1232
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001233** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
1234+
1235Panel will be shown below the change info block.
1236
Khai Do76c830c2016-07-28 16:35:45 -07001237** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
1238+
1239Panel will be shown below the related info block.
1240
Khai Do83940ba2016-09-20 15:15:45 +02001241** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
1242+
1243Panel will be shown in the history bar on the right side of the buttons.
1244
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001245** The following parameters are provided:
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001246*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001247+
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001248The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
1249current change.
David Ostrovsky916ae0c2016-03-15 17:05:41 +01001250+
1251The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
1252the current patch set.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001253
Edwin Kempin88b947a2015-07-08 09:03:56 +02001254* Project Info Screen:
1255** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
1256+
1257Panel will be shown at the top of the screen.
1258
1259** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
1260+
1261Panel will be shown at the bottom of the screen.
1262
1263** The following parameters are provided:
1264*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
1265+
1266The name of the project.
1267
Edwin Kempin241d9db2015-07-08 13:53:50 +02001268* User Password Screen:
1269** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
1270+
1271Panel will be shown at the bottom of the screen.
1272
Edwin Kempin30c6f472015-07-09 14:27:52 +02001273** The following parameters are provided:
1274*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1275+
1276The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1277the current user.
1278
Edwin Kempin1cd95f92015-07-14 08:27:20 +02001279* User Preferences Screen:
1280** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
1281+
1282Panel will be shown at the bottom of the screen.
1283
1284** The following parameters are provided:
1285*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1286+
1287The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1288the current user.
1289
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001290* User Profile Screen:
1291** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
1292+
1293Panel will be shown at the bottom of the screen below the grid with the
1294profile data.
1295
Edwin Kempin30c6f472015-07-09 14:27:52 +02001296** The following parameters are provided:
1297*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1298+
1299The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1300the current user.
1301
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001302Example panel:
1303[source,java]
1304----
1305public class MyPlugin extends PluginEntryPoint {
1306 @Override
1307 public void onPluginLoad() {
1308 Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001309 "my_panel_name",
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001310 new Panel.EntryPoint() {
1311 @Override
1312 public void onLoad(Panel panel) {
1313 panel.setWidget(new InlineLabel("My Panel for change "
1314 + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
1315 }
1316 });
1317 }
1318}
1319----
1320
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001321Change Screen panel ordering may be specified in the
1322project config. Values may be either "plugin name" or
1323"plugin name"."panel name".
1324Panels not specified in the config will be added
1325to the end in load order. Panels specified in the config that
1326are not found will be ignored.
1327
1328Example config:
1329----
1330[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
1331 panel = helloworld.change_id
1332 panel = myotherplugin
1333 panel = myplugin.my_panel_name
1334----
1335
1336
1337
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001338[[actions]]
1339=== Actions
1340
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001341Plugins can contribute UI actions on core Gerrit pages. This is useful
1342for workflow customization or exposing plugin functionality through the
1343UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001344
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001345For instance a plugin to integrate Jira with Gerrit changes may
1346contribute a "File bug" button to allow filing a bug from the change
1347page or plugins to integrate continuous integration systems may
1348contribute a "Schedule" button to allow a CI build to be scheduled
1349manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001350
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001351Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001352
1353* Change screen
1354* Project info screen
1355
1356Plugins contribute UI actions by implementing the `UiAction` interface:
1357
David Pursehouse68153d72013-09-04 10:09:17 +09001358[source,java]
1359----
1360@RequiresCapability("printHello")
1361class HelloWorldAction implements UiAction<RevisionResource>,
1362 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1363 static class Input {
1364 boolean french;
1365 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001366 }
David Pursehouse68153d72013-09-04 10:09:17 +09001367
1368 private Provider<CurrentUser> user;
1369
1370 @Inject
1371 HelloWorldAction(Provider<CurrentUser> user) {
1372 this.user = user;
1373 }
1374
1375 @Override
1376 public String apply(RevisionResource rev, Input input) {
1377 final String greeting = input.french
1378 ? "Bonjour"
1379 : "Hello";
1380 return String.format("%s %s from change %s, patch set %d!",
1381 greeting,
1382 Strings.isNullOrEmpty(input.message)
1383 ? Objects.firstNonNull(user.get().getUserName(), "world")
1384 : input.message,
1385 rev.getChange().getId().toString(),
1386 rev.getPatchSet().getPatchSetId());
1387 }
1388
1389 @Override
1390 public Description getDescription(
1391 RevisionResource resource) {
1392 return new Description()
1393 .setLabel("Say hello")
1394 .setTitle("Say hello in different languages");
1395 }
1396}
1397----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001398
David Ostrovsky450eefe2013-10-21 21:18:11 +02001399Sometimes plugins may want to be able to change the state of a patch set or
1400change in the `UiAction.apply()` method and reflect these changes on the core
1401UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1402patch set panel may want to disable that button after the build was scheduled
1403and update the tooltip of that button. But because of Gerrit's caching
1404strategy the following must be taken into consideration.
1405
1406The browser is allowed to cache the `UiAction` information until something on
1407the change is modified. More accurately the change row needs to be modified in
1408the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1409the +refs/meta/config+ of the project or any parents needs to change to a new
1410SHA-1. The ETag SHA-1 computation code can be found in the
1411`ChangeResource.getETag()` method.
1412
David Pursehoused128c892013-10-22 21:52:21 +09001413The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001414
1415[source,java]
1416----
1417@Override
1418public Object apply(RevisionResource rcrs, Input in) {
1419 // schedule a build
1420 [...]
1421 // update change
1422 ReviewDb db = dbProvider.get();
Edwin Kempine2d06b02016-02-17 18:34:17 +01001423 try (BatchUpdate bu = batchUpdateFactory.create(
1424 db, project.getNameKey(), user, TimeUtil.nowTs())) {
1425 bu.addOp(change.getId(), new BatchUpdate.Op() {
1426 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001427 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001428 return true;
1429 }
1430 });
1431 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001432 }
David Pursehoused128c892013-10-22 21:52:21 +09001433 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001434}
1435----
1436
David Ostrovskyf86bae52013-09-01 09:10:39 +02001437`UiAction` must be bound in a plugin module:
1438
David Pursehouse68153d72013-09-04 10:09:17 +09001439[source,java]
1440----
1441public class Module extends AbstractModule {
1442 @Override
1443 protected void configure() {
1444 install(new RestApiModule() {
1445 @Override
1446 protected void configure() {
1447 post(REVISION_KIND, "say-hello")
1448 .to(HelloWorldAction.class);
1449 }
1450 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001451 }
David Pursehouse68153d72013-09-04 10:09:17 +09001452}
1453----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001454
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001455The module above must be declared in the `pom.xml` for Maven driven
1456plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001457
David Pursehouse68153d72013-09-04 10:09:17 +09001458[source,xml]
1459----
1460<manifestEntries>
1461 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1462</manifestEntries>
1463----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001464
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001465or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001466
David Pursehouse68153d72013-09-04 10:09:17 +09001467[source,python]
1468----
1469manifest_entries = [
1470 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1471]
1472----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001473
1474In some use cases more user input must be gathered, for that `UiAction` can be
1475combined with the JavaScript API. This would display a small popup near the
1476activation button to gather additional input from the user. The JS file is
1477typically put in the `static` folder within the plugin's directory:
1478
David Pursehouse68153d72013-09-04 10:09:17 +09001479[source,javascript]
1480----
1481Gerrit.install(function(self) {
1482 function onSayHello(c) {
1483 var f = c.textfield();
1484 var t = c.checkbox();
1485 var b = c.button('Say hello', {onclick: function(){
1486 c.call(
1487 {message: f.value, french: t.checked},
1488 function(r) {
1489 c.hide();
1490 window.alert(r);
1491 c.refresh();
1492 });
1493 }});
1494 c.popup(c.div(
1495 c.prependLabel('Greeting message', f),
1496 c.br(),
1497 c.label(t, 'french'),
1498 c.br(),
1499 b));
1500 f.focus();
1501 }
1502 self.onAction('revision', 'say-hello', onSayHello);
1503});
1504----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001505
1506The JS module must be exposed as a `WebUiPlugin` and bound as
1507an HTTP Module:
1508
David Pursehouse68153d72013-09-04 10:09:17 +09001509[source,java]
1510----
1511public class HttpModule extends HttpPluginModule {
1512 @Override
1513 protected void configureServlets() {
1514 DynamicSet.bind(binder(), WebUiPlugin.class)
1515 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001516 }
David Pursehouse68153d72013-09-04 10:09:17 +09001517}
1518----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001519
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001520The HTTP module above must be declared in the `pom.xml` for Maven
1521driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001522
David Pursehouse68153d72013-09-04 10:09:17 +09001523[source,xml]
1524----
1525<manifestEntries>
1526 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1527</manifestEntries>
1528----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001529
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001530or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001531
David Pursehouse68153d72013-09-04 10:09:17 +09001532[source,python]
1533----
1534manifest_entries = [
1535 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1536]
1537----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001538
1539If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1540capability check is done during the `UiAction` gathering, so the plugin author
1541doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1542case.
1543
David Pursehousea61ee502016-09-06 16:27:09 +09001544The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001545
1546* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001547* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001548* user is a member of a group which has the required capability
1549
1550The `apply` method is called when the button is clicked. If `UiAction` is
1551combined with JavaScript API (its own JavaScript function is provided),
1552then a popup dialog is normally opened to gather additional user input.
1553A new button is placed on the popup dialog to actually send the request.
1554
1555Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1556can be accessed from any REST client, i. e.:
1557
Michael Ochmannb99feab2016-07-06 14:10:22 +02001558----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001559 curl -X POST -H "Content-Type: application/json" \
1560 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001561 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001562 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1563 "Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001564----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001565
David Pursehouse42245822013-09-24 09:48:20 +09001566A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001567particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001568
1569[source,java]
1570----
1571public class Module extends AbstractModule {
1572 @Override
1573 protected void configure() {
1574 install(new RestApiModule() {
1575 @Override
1576 protected void configure() {
1577 delete(PROJECT_KIND)
1578 .to(DeleteProject.class);
1579 }
1580 });
1581 }
1582}
1583----
1584
David Pursehouse42245822013-09-24 09:48:20 +09001585For a `UiAction` bound this way, a JS API function can be provided.
1586
1587Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001588can be bound per resource without view name. To define a JS function
1589for the `UiAction`, "/" must be used as the name:
1590
1591[source,javascript]
1592----
1593Gerrit.install(function(self) {
1594 function onDeleteProject(c) {
1595 [...]
1596 }
1597 self.onAction('project', '/', onDeleteProject);
1598});
1599----
1600
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001601
1602[[action-visitor]]
1603=== Action Visitors
1604
1605In addition to providing new actions, plugins can have fine-grained control
1606over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1607removing existing actions, including those contributed by core.
1608
1609Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1610which is mutable, along with copies of the
1611link:rest-api-changes.html#change-info[ChangeInfo] and
1612link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1613action, or return `false` to exclude it from the resulting map.
1614
1615These operations only affect the action buttons that are displayed in the UI;
1616the underlying REST API endpoints are not affected. Multiple plugins may
1617implement the visitor interface, but the order in which they are run is
1618undefined.
1619
1620For example, to exclude "Cherry-Pick" only from certain projects, and rename
1621"Abandon":
1622
1623[source,java]
1624----
1625public class MyActionVisitor implements ActionVisitor {
1626 @Override
1627 public boolean visit(String name, ActionInfo actionInfo,
1628 ChangeInfo changeInfo) {
1629 if (name.equals("abandon")) {
1630 actionInfo.label = "Drop";
1631 }
1632 return true;
1633 }
1634
1635 @Override
1636 public boolean visit(String name, ActionInfo actionInfo,
1637 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1638 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1639 return false;
1640 }
1641 return true;
1642 }
1643}
1644----
1645
1646
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001647[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001648== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001649
1650Plugins can contribute items to Gerrit's top menu.
1651
1652A single top menu extension can have multiple elements and will be put as
1653the last element in Gerrit's top menu.
1654
1655Plugins define the top menu entries by implementing `TopMenu` interface:
1656
1657[source,java]
1658----
1659public class MyTopMenuExtension implements TopMenu {
1660
1661 @Override
1662 public List<MenuEntry> getEntries() {
1663 return Lists.newArrayList(
1664 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1665 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1666 }
1667}
1668----
1669
Edwin Kempin77f23242013-09-30 14:53:20 +02001670Plugins can also add additional menu items to Gerrit's top menu entries
1671by defining a `MenuEntry` that has the same name as a Gerrit top menu
1672entry:
1673
1674[source,java]
1675----
1676public class MyTopMenuExtension implements TopMenu {
1677
1678 @Override
1679 public List<MenuEntry> getEntries() {
1680 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001681 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001682 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1683 }
1684}
1685----
1686
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001687`MenuItems` that are bound for the `MenuEntry` with the name
1688`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1689which is automatically replaced by the actual project name.
1690
1691E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1692specific requests and add an menu item for this:
1693
1694[source,java]
1695---
1696 new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1697---
1698
1699This also enables plugins to provide menu items for project aware
1700screens:
1701
1702[source,java]
1703---
1704 new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1705---
1706
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001707If no Guice modules are declared in the manifest, the top menu extension may use
1708auto-registration by providing an `@Listen` annotation:
1709
1710[source,java]
1711----
1712@Listen
1713public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001714 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001715}
1716----
1717
Luca Milanesiocb230402013-10-11 08:49:56 +01001718Otherwise the top menu extension must be bound in the plugin module used
1719for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001720
1721[source,java]
1722----
Luca Milanesiocb230402013-10-11 08:49:56 +01001723package com.googlesource.gerrit.plugins.helloworld;
1724
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001725public class HelloWorldModule extends AbstractModule {
1726 @Override
1727 protected void configure() {
1728 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1729 }
1730}
1731----
1732
Luca Milanesiocb230402013-10-11 08:49:56 +01001733[source,manifest]
1734----
1735Gerrit-ApiType: plugin
1736Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1737----
1738
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001739It is also possible to show some menu entries only if the user has a
1740certain capability:
1741
1742[source,java]
1743----
1744public class MyTopMenuExtension implements TopMenu {
1745 private final String pluginName;
1746 private final Provider<CurrentUser> userProvider;
1747 private final List<MenuEntry> menuEntries;
1748
1749 @Inject
1750 public MyTopMenuExtension(@PluginName String pluginName,
1751 Provider<CurrentUser> userProvider) {
1752 this.pluginName = pluginName;
1753 this.userProvider = userProvider;
1754 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1755
1756 // add menu entry that is only visible to users with a certain capability
1757 if (canSeeMenuEntry()) {
1758 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1759 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1760 }
1761
1762 // add menu entry that is visible to all users (even anonymous users)
1763 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1764 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1765 }
1766
1767 private boolean canSeeMenuEntry() {
1768 if (userProvider.get().isIdentifiedUser()) {
1769 CapabilityControl ctl = userProvider.get().getCapabilities();
1770 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1771 || ctl.canAdministrateServer();
1772 } else {
1773 return false;
1774 }
1775 }
1776
1777 @Override
1778 public List<MenuEntry> getEntries() {
1779 return menuEntries;
1780 }
1781}
1782----
1783
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001784
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001785[[gwt_ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001786== GWT UI Extension
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001787Plugins can extend the Gerrit UI with own GWT code.
1788
Edwin Kempinb74daa92013-11-11 11:28:16 +01001789A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1790that bundles together all the configuration settings of the GWT plugin:
1791
1792[source,xml]
1793----
1794<?xml version="1.0" encoding="UTF-8"?>
1795<module rename-to="hello_gwt_plugin">
1796 <!-- Inherit the core Web Toolkit stuff. -->
1797 <inherits name="com.google.gwt.user.User"/>
1798 <!-- Other module inherits -->
1799 <inherits name="com.google.gerrit.Plugin"/>
1800 <inherits name="com.google.gwt.http.HTTP"/>
1801 <!-- Using GWT built-in themes adds a number of static -->
1802 <!-- resources to the plugin. No theme inherits lines were -->
1803 <!-- added in order to make this plugin as simple as possible -->
1804 <!-- Specify the app entry point class. -->
1805 <entry-point class="${package}.client.HelloPlugin"/>
1806 <stylesheet src="hello.css"/>
1807</module>
1808----
1809
1810The GWT module must inherit `com.google.gerrit.Plugin` and
1811`com.google.gwt.http.HTTP`.
1812
1813To register the GWT module a `GwtPlugin` needs to be bound.
1814
1815If no Guice modules are declared in the manifest, the GWT plugin may
1816use auto-registration by using the `@Listen` annotation:
1817
1818[source,java]
1819----
1820@Listen
1821public class MyExtension extends GwtPlugin {
1822 public MyExtension() {
1823 super("hello_gwt_plugin");
1824 }
1825}
1826----
1827
1828Otherwise the binding must be done in an `HttpModule`:
1829
1830[source,java]
1831----
1832public class HttpModule extends HttpPluginModule {
1833
1834 @Override
1835 protected void configureServlets() {
1836 DynamicSet.bind(binder(), WebUiPlugin.class)
1837 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1838 }
1839}
1840----
1841
1842The HTTP module above must be declared in the `pom.xml` for Maven
1843driven plugins:
1844
1845[source,xml]
1846----
1847<manifestEntries>
1848 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1849</manifestEntries>
1850----
1851
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001852The name that is provided to the `GwtPlugin` must match the GWT
1853module name compiled into the plugin. The name of the GWT module
1854can be explicitly set in the GWT module XML file by specifying
1855the `rename-to` attribute on the module. It is important that the
1856module name be unique across all plugins installed on the server,
1857as the module name determines the JavaScript namespace used by the
1858compiled plugin code.
Edwin Kempinb74daa92013-11-11 11:28:16 +01001859
1860[source,xml]
1861----
1862<module rename-to="hello_gwt_plugin">
1863----
1864
1865The actual GWT code must be implemented in a class that extends
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001866`com.google.gerrit.plugin.client.PluginEntryPoint`:
Edwin Kempinb74daa92013-11-11 11:28:16 +01001867
1868[source,java]
1869----
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001870public class HelloPlugin extends PluginEntryPoint {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001871
1872 @Override
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001873 public void onPluginLoad() {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001874 // Create the dialog box
1875 final DialogBox dialogBox = new DialogBox();
1876
1877 // The content of the dialog comes from a User specified Preference
1878 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1879 dialogBox.setAnimationEnabled(true);
1880 Button closeButton = new Button("Close");
1881 VerticalPanel dialogVPanel = new VerticalPanel();
1882 dialogVPanel.setWidth("100%");
1883 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1884 dialogVPanel.add(closeButton);
1885
1886 closeButton.addClickHandler(new ClickHandler() {
1887 public void onClick(ClickEvent event) {
1888 dialogBox.hide();
1889 }
1890 });
1891
1892 // Set the contents of the Widget
1893 dialogBox.setWidget(dialogVPanel);
1894
1895 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1896 rootPanel.getElement().removeAttribute("href");
1897 rootPanel.addDomHandler(new ClickHandler() {
1898 @Override
1899 public void onClick(ClickEvent event) {
1900 dialogBox.center();
1901 dialogBox.show();
1902 }
1903 }, ClickEvent.getType());
1904 }
1905}
1906----
1907
1908This class must be set as entry point in the GWT module:
1909
1910[source,xml]
1911----
1912<entry-point class="${package}.client.HelloPlugin"/>
1913----
1914
1915In addition this class must be defined as module in the `pom.xml` for the
1916`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1917must be set to `${project.build.directory}/classes/static`:
1918
1919[source,xml]
1920----
1921<plugin>
1922 <groupId>org.codehaus.mojo</groupId>
1923 <artifactId>gwt-maven-plugin</artifactId>
David Pursehouse7ab81732015-05-07 12:00:47 +09001924 <version>2.7.0</version>
Edwin Kempinb74daa92013-11-11 11:28:16 +01001925 <configuration>
1926 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1927 <disableClassMetadata>true</disableClassMetadata>
1928 <disableCastChecking>true</disableCastChecking>
1929 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1930 </configuration>
1931 <executions>
1932 <execution>
1933 <goals>
1934 <goal>compile</goal>
1935 </goals>
1936 </execution>
1937 </executions>
1938</plugin>
1939----
1940
1941To attach a GWT widget defined by the plugin to the Gerrit core UI
1942`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1943Gerrit core widgets:
1944
1945[source,java]
1946----
1947RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1948rootPanel.getElement().removeAttribute("href");
1949rootPanel.addDomHandler(new ClickHandler() {
1950 @Override
1951 public void onClick(ClickEvent event) {
1952 dialogBox.center();
1953 dialogBox.show();
1954 }
1955}, ClickEvent.getType());
1956----
1957
1958GWT plugins can come with their own css file. This css file must have a
1959unique name and must be registered in the GWT module:
1960
1961[source,xml]
1962----
1963<stylesheet src="hello.css"/>
1964----
1965
Edwin Kempin2570b102013-11-11 11:44:50 +01001966If a GWT plugin wants to invoke the Gerrit REST API it can use
David Pursehouse3a388312014-02-25 16:41:47 +09001967`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
Edwin Kempin2570b102013-11-11 11:44:50 +01001968path and to trigger the REST calls.
1969
1970Example for invoking a Gerrit core REST endpoint:
1971
1972[source,java]
1973----
1974new RestApi("projects").id(projectName).view("description")
1975 .put("new description", new AsyncCallback<JavaScriptObject>() {
1976
1977 @Override
1978 public void onSuccess(JavaScriptObject result) {
1979 // TODO
1980 }
1981
1982 @Override
1983 public void onFailure(Throwable caught) {
1984 // never invoked
1985 }
1986});
1987----
1988
1989Example for invoking a REST endpoint defined by a plugin:
1990
1991[source,java]
1992----
1993new RestApi("projects").id(projectName).view("myplugin", "myview")
1994 .get(new AsyncCallback<JavaScriptObject>() {
1995
1996 @Override
1997 public void onSuccess(JavaScriptObject result) {
1998 // TODO
1999 }
2000
2001 @Override
2002 public void onFailure(Throwable caught) {
2003 // never invoked
2004 }
2005});
2006----
2007
2008The `onFailure(Throwable)` of the provided callback is never invoked.
2009If an error occurs, it is shown in an error dialog.
2010
2011In order to be able to do REST calls the GWT module must inherit
2012`com.google.gwt.json.JSON`:
2013
2014[source,xml]
2015----
2016<inherits name="com.google.gwt.json.JSON"/>
2017----
2018
Edwin Kempin15199792014-04-23 16:22:05 +02002019[[screen]]
Shawn Pearced5c844f2013-12-26 15:32:26 -08002020== Add Screen
Edwin Kempin15199792014-04-23 16:22:05 +02002021A link:#gwt_ui_extension[GWT plugin] can link:#top-menu-extensions[add
2022a menu item] that opens a screen that is implemented by the plugin.
2023This way plugin screens can be fully integrated into the Gerrit UI.
Shawn Pearced5c844f2013-12-26 15:32:26 -08002024
2025Example menu item:
2026[source,java]
2027----
2028public class MyMenu implements TopMenu {
2029 private final List<MenuEntry> menuEntries;
2030
2031 @Inject
2032 public MyMenu(@PluginName String name) {
David Pursehouseccdeae82016-05-03 23:16:15 +09002033 menuEntries = new ArrayList<>();
Shawn Pearced5c844f2013-12-26 15:32:26 -08002034 menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
2035 new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
2036 }
2037
2038 @Override
2039 public List<MenuEntry> getEntries() {
2040 return menuEntries;
2041 }
2042}
2043----
2044
2045Example screen:
2046[source,java]
2047----
2048public class MyPlugin extends PluginEntryPoint {
2049 @Override
2050 public void onPluginLoad() {
2051 Plugin.get().screen("my-screen", new Screen.EntryPoint() {
2052 @Override
2053 public void onLoad(Screen screen) {
2054 screen.add(new InlineLabel("My Screen");
2055 screen.show();
2056 }
2057 });
2058 }
2059}
2060----
2061
Edwin Kempin70e11122015-07-08 13:28:40 +02002062[[user-settings-screen]]
2063== Add User Settings Screen
2064
2065A link:#gwt_ui_extension[GWT plugin] can implement a user settings
2066screen that is integrated into the Gerrit user settings menu.
2067
2068Example settings screen:
2069[source,java]
2070----
2071public class MyPlugin extends PluginEntryPoint {
2072 @Override
2073 public void onPluginLoad() {
2074 Plugin.get().settingsScreen("my-preferences", "My Preferences",
2075 new Screen.EntryPoint() {
2076 @Override
2077 public void onLoad(Screen screen) {
2078 screen.setPageTitle("Settings");
2079 screen.add(new InlineLabel("My Preferences"));
2080 screen.show();
2081 }
2082 });
2083 }
2084}
2085----
2086
Edwin Kempinfa0d4942015-07-16 12:38:52 +02002087By defining an link:config-gerrit.html#urlAlias[urlAlias] Gerrit
2088administrators can map plugin screens into the Gerrit URL namespace or
2089even replace Gerrit screens by plugin screens.
2090
Edwin Kempinb1e6a3a2015-07-22 15:36:56 +02002091Plugins may also programatically add URL aliases in the preferences of
2092of a user. This way certain screens can be replaced for certain users.
2093E.g. the plugin may offer a user preferences setting for choosing a
2094screen that then sets/unsets a URL alias for the user.
2095
Edwin Kempin289f1a02014-02-04 16:08:25 +01002096[[settings-screen]]
2097== Plugin Settings Screen
2098
2099If a plugin implements a screen for administrating its settings that is
2100available under "#/x/<plugin-name>/settings" it is automatically linked
2101from the plugin list screen.
2102
Edwin Kempinf5a77332012-07-18 11:17:53 +02002103[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002104== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002105
2106Plugins or extensions may register additional HTTP servlets, and
2107wrap them with HTTP filters.
2108
2109Servlets may use auto-registration to declare the URL they handle:
2110
David Pursehouse68153d72013-09-04 10:09:17 +09002111[source,java]
2112----
2113import com.google.gerrit.extensions.annotations.Export;
2114import com.google.inject.Singleton;
2115import javax.servlet.http.HttpServlet;
2116import javax.servlet.http.HttpServletRequest;
2117import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002118
David Pursehouse68153d72013-09-04 10:09:17 +09002119@Export("/print")
2120@Singleton
2121class HelloServlet extends HttpServlet {
2122 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
2123 res.setContentType("text/plain");
2124 res.setCharacterEncoding("UTF-8");
2125 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002126 }
David Pursehouse68153d72013-09-04 10:09:17 +09002127}
2128----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002129
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002130The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07002131`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002132to register the HTTP servlets and declare it explicitly in the manifest
2133with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002134
David Pursehouse68153d72013-09-04 10:09:17 +09002135[source,java]
2136----
2137import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002138
David Pursehouse68153d72013-09-04 10:09:17 +09002139class MyWebUrls extends ServletModule {
2140 protected void configureServlets() {
2141 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002142 }
David Pursehouse68153d72013-09-04 10:09:17 +09002143}
2144----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002145
2146For a plugin installed as name `helloworld`, the servlet implemented
2147by HelloServlet class will be available to users as:
2148
2149----
2150$ curl http://review.example.com/plugins/helloworld/print
2151----
Nasser Grainawie033b262012-05-09 17:54:21 -07002152
Edwin Kempinf5a77332012-07-18 11:17:53 +02002153[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002154== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02002155
Dave Borowitz9e158752015-02-24 10:17:04 -08002156Plugins can request a data directory with a `@PluginData` Path (or File,
2157deprecated) dependency. A data directory will be created automatically
2158by the server in `$site_path/data/$plugin_name` and passed to the
2159plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02002160
2161Plugins can use this to store any data they want.
2162
David Pursehouse68153d72013-09-04 10:09:17 +09002163[source,java]
2164----
2165@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08002166MyType(@PluginData java.nio.file.Path myDir) {
2167 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09002168}
2169----
Edwin Kempin41f63912012-07-17 12:33:55 +02002170
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02002171[[secure-store]]
2172== SecureStore
2173
2174SecureStore allows to change the way Gerrit stores sensitive data like
2175passwords.
2176
2177In order to replace the default SecureStore (no-op) implementation,
2178a class that extends `com.google.gerrit.server.securestore.SecureStore`
2179needs to be provided (with dependencies) in a separate jar file. Then
2180link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
2181switch implementations.
2182
2183The SecureStore implementation is instantiated using a Guice injector
2184which binds the `File` annotated with the `@SitePath` annotation.
2185This means that a SecureStore implementation class can get access to
2186the `site_path` like in the following example:
2187
2188[source,java]
2189----
2190@Inject
2191MySecureStore(@SitePath java.io.File sitePath) {
2192 // your code
2193}
2194----
2195
2196No Guice bindings or modules are required. Gerrit will automatically
2197discover and bind the implementation.
2198
Michael Ochmann24612652016-02-12 17:26:18 +01002199[[accountcreation]]
2200== Account Creation
2201
2202Plugins can hook into the
2203link:rest-api-accounts.html#create-account[account creation] REST API and
2204inject additional external identifiers for an account that represents a user
2205in some external user store. For that, an implementation of the extension
2206point `com.google.gerrit.server.api.accounts.AccountExternalIdCreator`
2207must be registered.
2208
2209[source,java]
2210----
2211class MyExternalIdCreator implements AccountExternalIdCreator {
2212 @Override
2213 public List<AccountExternalId> create(Account.Id id, String username,
2214 String email) {
2215 // your code
2216 }
2217}
2218
2219bind(AccountExternalIdCreator.class)
2220 .annotatedWith(UniqueAnnotations.create())
2221 .to(MyExternalIdCreator.class);
2222}
2223----
2224
Edwin Kempinea621482013-10-16 12:58:24 +02002225[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002226== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002227
Edwin Kempineafde882015-05-11 15:40:44 +02002228Gerrit offers commands for downloading changes and cloning projects
2229using different download schemes (e.g. for downloading via different
2230network protocols). Plugins can contribute download schemes, download
2231commands and clone commands by implementing
2232`com.google.gerrit.extensions.config.DownloadScheme`,
2233`com.google.gerrit.extensions.config.DownloadCommand` and
2234`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002235
Edwin Kempineafde882015-05-11 15:40:44 +02002236The download schemes, download commands and clone commands which are
2237used most often are provided by the Gerrit core plugin
2238`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002239
Edwin Kempin78279ba2015-05-22 15:22:41 +02002240[[included-in]]
2241== Included In
2242
2243For merged changes the link:user-review-ui.html#included-in[Included In]
2244drop-down panel shows the branches and tags in which the change is
2245included.
2246
2247Plugins can add additional systems in which the change can be included
2248by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2249e.g. a plugin can provide a list of servers on which the change was
2250deployed.
2251
Sven Selbergae1a10c2014-02-14 14:24:29 +01002252[[links-to-external-tools]]
2253== Links To External Tools
2254
2255Gerrit has extension points that enables development of a
2256light-weight plugin that links commits to external
2257tools (GitBlit, CGit, company specific resources etc).
2258
2259PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
2260
2261[source, java]
2262----
2263import com.google.gerrit.extensions.annotations.Listen;
2264import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002265import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002266
2267@Listen
2268public class MyWeblinkPlugin implements PatchSetWebLink {
2269
2270 private String name = "MyLink";
2271 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002272 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002273
2274 @Override
Jonathan Niederb3cd6902015-03-12 16:19:15 -07002275 public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002276 return new WebLinkInfo(name,
2277 imageUrl,
2278 String.format(placeHolderUrlProjectCommit, project, commit),
2279 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002280 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002281}
2282----
2283
David Pursehouse58b8d762016-12-09 11:12:27 +09002284ParentWebLinks will appear to the right of the SHA1 of the parent
2285revisions in the UI. The implementation should in most use cases direct
2286to the same external service as PatchSetWebLink; it is provided as a
2287separate interface because not all users want to have links for the
2288parent revisions.
2289
Edwin Kempinb3696c82014-09-11 09:41:42 +02002290FileWebLinks will appear in the side-by-side diff screen on the right
2291side of the patch selection on each side.
2292
Edwin Kempin8cdce502014-12-06 10:55:38 +01002293DiffWebLinks will appear in the side-by-side and unified diff screen in
2294the header next to the navigation icons.
2295
Edwin Kempinea004752014-04-11 15:56:02 +02002296ProjectWebLinks will appear in the project list in the
2297`Repository Browser` column.
2298
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002299BranchWebLinks will appear in the branch list in the last column.
2300
Edwin Kempinf82b8122016-06-03 09:20:16 +02002301FileHistoryWebLinks will appear on the access rights screen.
2302
Saša Živkovca7a67e2015-12-01 14:25:10 +01002303[[lfs-extension]]
2304== LFS Storage Plugins
2305
David Pursehouse2463c542016-08-02 16:04:58 +09002306Gerrit provides an extension point that enables development of
2307link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
2308LFS (Large File Storage)] storage plugins. Gerrit core exposes the default LFS
2309protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2310to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2311the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2312used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002313
2314[source, java]
2315----
2316/** Provide an LFS protocol implementation */
2317import org.eclipse.jgit.lfs.server.LargeFileRepository;
2318import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2319
2320@Singleton
2321public class LfsApiServlet extends LfsProtocolServlet {
2322 private static final long serialVersionUID = 1L;
2323
2324 private final S3LargeFileRepository repository;
2325
2326 @Inject
2327 LfsApiServlet(S3LargeFileRepository repository) {
2328 this.repository = repository;
2329 }
2330
2331 @Override
2332 protected LargeFileRepository getLargeFileRepository() {
2333 return repository;
2334 }
2335}
2336
2337/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2338import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2339
2340import com.google.gerrit.httpd.plugins.HttpPluginModule;
2341
2342public class HttpModule extends HttpPluginModule {
2343
2344 @Override
2345 protected void configureServlets() {
2346 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2347 }
2348}
2349
2350/** Provide an implementation of the LargeFileRepository */
2351import org.eclipse.jgit.lfs.server.s3.S3Repository;
2352
2353public class S3LargeFileRepository extends S3Repository {
2354...
2355}
2356----
2357
David Pursehouse8ad11732016-08-29 15:00:14 +09002358[[metrics]]
2359== Metrics
2360
2361=== Metrics Reporting
2362
2363To send Gerrit's metrics data to an external reporting backend, a plugin can
2364get a `MetricRegistry` injected and register an instance of a class that
2365implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
2366DropWizard Metrics].
2367
2368Metric reporting plugin implementations are provided for
2369link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX],
2370link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search],
2371and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite].
2372
2373There is also a working example of reporting metrics to the console in the
2374link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+/master/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.jave[
2375cookbook plugin].
2376
2377=== Providing own metrics
2378
2379Plugins may provide metrics to be dispatched to external reporting services by
2380getting a `MetricMaker` injected and creating instances of specific types of
2381metric:
2382
2383* Counter
2384+
2385Metric whose value increments during the life of the process.
2386
2387* Timer
2388+
2389Metric recording time spent on an operation.
2390
2391* Histogram
2392+
2393Metric recording statistical distribution (rate) of values.
2394
David Pursehouse48d05ea2017-02-03 19:05:29 +09002395Note that metrics cannot be recorded from plugin init steps that
2396are run during site initialization.
2397
David Pursehousec3bbd562017-02-06 20:25:29 +09002398By default, plugin metrics are recorded under
2399`plugins/${plugin-name}/${metric-name}`. This can be changed by
2400setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2401file. For example:
2402
2403----
2404 [plugin "my-plugin"]
2405 metricsPrefix = my-metrics
2406----
2407
2408will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002409
2410See the replication metrics in the
2411link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
2412replication plugin] for an example of usage.
2413
Edwin Kempinda17bc32016-06-14 11:50:58 +02002414[[account-patch-review-store]]
2415== AccountPatchReviewStore
2416
2417The AccountPatchReviewStore is used to store reviewed flags on changes.
2418A reviewed flag is a tuple of (patch set ID, file, account ID) and
2419records whether the user has reviewed a file in a patch set. Each user
2420can easily have thousands of reviewed flags and the number of reviewed
2421flags is growing without bound. The store must be able handle this data
2422volume efficiently.
2423
2424Gerrit implements this extension point, but plugins may bind another
2425implementation, e.g. one that supports multi-master.
2426
2427----
2428DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2429 .to(MultiMasterAccountPatchReviewStore.class);
2430
2431...
2432
2433public class MultiMasterAccountPatchReviewStore
2434 implements AccountPatchReviewStore {
2435 ...
2436}
2437----
2438
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002439
Edwin Kempinf5a77332012-07-18 11:17:53 +02002440[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002441== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002442
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002443If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002444`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002445automatically export these resources over HTTP from the plugin JAR.
2446
David Pursehouse6853b5a2013-07-10 11:38:03 +09002447Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002448available as `/plugins/helloworld/static/resource`. This prefix is
2449configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002450
David Pursehouse6853b5a2013-07-10 11:38:03 +09002451Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002452will be available as `/plugins/helloworld/Documentation/resource`. This
2453prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2454attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002455
Christian Aistleitner040cf822015-03-26 21:09:09 +01002456Documentation may be written in the Markdown flavor
2457link:https://github.com/sirthias/pegdown[pegdown]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002458if the file name ends with `.md`. Gerrit will automatically convert
2459Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002460
Edwin Kempinf5a77332012-07-18 11:17:53 +02002461[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002462Within the Markdown documentation files macros can be used that allow
2463to write documentation with reasonably accurate examples that adjust
2464automatically based on the installation.
2465
2466The following macros are supported:
2467
2468[width="40%",options="header"]
2469|===================================================
2470|Macro | Replacement
2471|@PLUGIN@ | name of the plugin
2472|@URL@ | Gerrit Web URL
2473|@SSH_HOST@ | SSH Host
2474|@SSH_PORT@ | SSH Port
2475|===================================================
2476
2477The macros will be replaced when the documentation files are rendered
2478from Markdown to HTML.
2479
2480Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2481even if there is an expansion for `KEEP` in the future.
2482
Edwin Kempinf5a77332012-07-18 11:17:53 +02002483[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002484=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002485
2486If a plugin does not handle its `/` URL itself, Gerrit will
2487redirect clients to the plugin's `/Documentation/index.html`.
2488Requests for `/Documentation/` (bare directory) will also redirect
2489to `/Documentation/index.html`.
2490
2491If neither resource `Documentation/index.html` or
2492`Documentation/index.md` exists in the plugin JAR, Gerrit will
2493automatically generate an index page for the plugin's documentation
2494tree by scanning every `*.md` and `*.html` file in the Documentation/
2495directory.
2496
2497For any discovered Markdown (`*.md`) file, Gerrit will parse the
2498header of the file and extract the first level one title. This
2499title text will be used as display text for a link to the HTML
2500version of the page.
2501
2502For any discovered HTML (`*.html`) file, Gerrit will use the name
2503of the file, minus the `*.html` extension, as the link text. Any
2504hyphens in the file name will be replaced with spaces.
2505
David Pursehouse6853b5a2013-07-10 11:38:03 +09002506If a discovered file is named `about.md` or `about.html`, its
2507content will be inserted in an 'About' section at the top of the
2508auto-generated index page. If both `about.md` and `about.html`
2509exist, only the first discovered file will be used.
2510
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002511If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09002512into a 'Commands' section of the generated index page.
2513
David Pursehousefe529152013-08-14 16:35:06 +09002514If a discovered file name beings with `servlet-` it will be clustered
2515into a 'Servlets' section of the generated index page.
2516
2517If a discovered file name beings with `rest-api-` it will be clustered
2518into a 'REST APIs' section of the generated index page.
2519
David Pursehouse6853b5a2013-07-10 11:38:03 +09002520All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002521
2522Some optional information from the manifest is extracted and
2523displayed as part of the index page, if present in the manifest:
2524
2525[width="40%",options="header"]
2526|===================================================
2527|Field | Source Attribute
2528|Name | Implementation-Title
2529|Vendor | Implementation-Vendor
2530|Version | Implementation-Version
2531|URL | Implementation-URL
2532|API Version | Gerrit-ApiVersion
2533|===================================================
2534
Edwin Kempinf5a77332012-07-18 11:17:53 +02002535[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002536== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002537
Edwin Kempinf7295742012-07-16 15:03:46 +02002538Compiled plugins and extensions can be deployed to a running Gerrit
2539server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002540
David Pursehousea1d633b2014-05-02 17:21:02 +09002541Web UI plugins distributed as single `.js` file can be deployed
Dariusz Luksza357a2422012-11-12 06:16:26 +01002542without the overhead of JAR packaging, for more information refer to
2543link:cmd-plugin-install.html[plugin install] command.
2544
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002545Plugins can also be copied directly into the server's
Dariusz Luksza357a2422012-11-12 06:16:26 +01002546directory at `$site_path/plugins/$name.(jar|js)`. The name of
2547the JAR file, minus the `.jar` or `.js` extension, will be used as the
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002548plugin name. Unless disabled, servers periodically scan this
2549directory for updated plugins. The time can be adjusted by
2550link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002551
Edwin Kempinf7295742012-07-16 15:03:46 +02002552For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2553command can be used.
2554
Brad Larsond5e87c32012-07-11 12:18:49 -05002555Disabled plugins can be re-enabled using the
2556link:cmd-plugin-enable.html[plugin enable] command.
2557
Edwin Kempinc1a25102015-06-22 14:47:36 +02002558== Known issues and bugs
2559
2560=== Error handling in UI when using the REST API
2561
2562When a plugin invokes a REST endpoint in the UI, it provides an
2563`AsyncCallback` to handle the result. At the moment the
2564`onFailure(Throwable)` of the callback is never invoked, even if there
2565is an error. Errors are always handled by the Gerrit core UI which
2566shows the error dialog. This means currently plugins cannot do any
2567error handling and e.g. ignore expected errors.
2568
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002569In the following example the REST endpoint would return '404 Not
2570Found' if the user has no username and the Gerrit core UI would
2571display an error dialog for this. However having no username is
2572not an error and the plugin may like to handle this case.
Edwin Kempinc1a25102015-06-22 14:47:36 +02002573
2574[source,java]
2575----
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002576new RestApi("accounts").id("self").view("username")
Edwin Kempinc1a25102015-06-22 14:47:36 +02002577 .get(new AsyncCallback<NativeString>() {
2578
2579 @Override
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002580 public void onSuccess(NativeString username) {
Edwin Kempinc1a25102015-06-22 14:47:36 +02002581 // TODO
2582 }
2583
2584 @Override
2585 public void onFailure(Throwable caught) {
2586 // never invoked
2587 }
2588});
2589----
2590
2591
Patrick Hiesel87880b02016-05-03 18:15:08 +02002592[[reviewer-suggestion]]
2593== Reviewer Suggestion Plugins
2594
2595Gerrit provides an extension point that enables Plugins to rank
2596the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2597the change screen.
2598Gerrit supports both a default suggestion that appears when the user has not yet
2599typed anything and a filtered suggestion that is shown as the user starts
2600typing.
2601Plugins receive a candidate list and can return a Set of suggested reviewers
2602containing the Account.Id and a score for each reviewer.
2603The candidate list is non-binding and plugins can choose to return reviewers not
2604initially contained in the candidate list.
2605Server administrators can configure the overall weight of each plugin using the
2606weight config parameter on [addreviewer "<pluginName-exportName>"].
2607
2608[source, java]
2609----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002610import com.google.gerrit.common.Nullable;
2611import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002612import com.google.gerrit.reviewdb.client.Account;
2613import com.google.gerrit.reviewdb.client.Change;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002614import com.google.gerrit.reviewdb.client.Project;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002615
2616import java.util.Set;
2617
2618public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002619 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2620 @Nullable Change.Id changeId, @Nullable String query,
2621 Set<Account.Id> candidates) {
2622 Set<SuggestedReviewer> suggestions = new HashSet<>();
2623 // Implement your ranking logic here
2624 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002625 }
2626}
2627----
2628
2629
Patrick Hiesel30c76182017-01-20 11:46:43 +01002630[[mail-filter]]
2631== Mail Filter Plugins
2632
2633Gerrit provides an extension point that enables Plugins to discard incoming
2634messages and prevent further processing by Gerrit.
2635
David Pursehouse4b067752017-03-03 15:54:53 +09002636This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002637specific checks like IP filters.
2638
2639[source, java]
2640----
2641import com.google.gerrit.extensions.annotations.ExtensionPoint;
2642import com.google.gerrit.server.mail.receive.MailMessage;
2643
2644public class MyPlugin implements MailFilter {
2645 boolean shouldProcessMessage(MailMessage message) {
2646 // Implement your filter logic here
2647 return true;
2648 }
2649}
2650----
2651
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002652== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002653
2654* link:js-api.html[JavaScript API]
2655* link:dev-rest-api.html[REST API Developers' Notes]
2656
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002657GERRIT
2658------
2659Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002660
2661SEARCHBOX
2662---------