blob: 206b66411bca87ddfb961e82e8f58593c9b8d4aa [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
David Pursehouse62864b72013-10-17 23:05:08 +090041 -DarchetypeVersion=2.9-SNAPSHOT \
Edwin Kempin91155c22013-12-02 20:25:18 +010042 -DgroupId=com.googlesource.gerrit.plugins.testplugin \
43 -DartifactId=testplugin
Edwin Kempinf878c4b2012-07-18 09:34:25 +020044----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200135[[plugin_name]]
136Plugin Name
137~~~~~~~~~~~
138
David Pursehoused128c892013-10-22 21:52:21 +0900139A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200140
141====
142 Gerrit-PluginName: replication
143====
144
145This is useful for plugins that contribute plugin-owned capabilities that
146are stored in the `project.config` file. Another use case is to be able to put
147project specific plugin configuration section in `project.config`. In this
148case it is advantageous to reserve the plugin name to access the configuration
149section in the `project.config` file.
150
151If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
152the plugin file name.
153
154If a plugin provides its own name, then that plugin cannot be deployed
155multiple times under different file names on one Gerrit site.
156
157For Maven driven plugins, the following line must be included in the pom.xml
158file:
159
160[source,xml]
161----
162<manifestEntries>
163 <Gerrit-PluginName>name</Gerrit-PluginName>
164</manifestEntries>
165----
166
167For Buck driven plugins, the following line must be included in the BUCK
168configuration file:
169
170[source,python]
171----
David Pursehouse529ec252013-09-27 13:45:14 +0900172manifest_entries = [
173 'Gerrit-PluginName: name',
174]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200175----
176
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200177A plugin can get its own name injected at runtime:
178
179[source,java]
180----
181public class MyClass {
182
183 private final String pluginName;
184
185 @Inject
186 public MyClass(@PluginName String pluginName) {
187 this.pluginName = pluginName;
188 }
189
David Pursehoused128c892013-10-22 21:52:21 +0900190 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200191}
192----
193
David Pursehouse8ed0d922013-10-18 18:57:56 +0900194A plugin can get its canonical web URL injected at runtime:
195
196[source,java]
197----
198public class MyClass {
199
200 private final String url;
201
202 @Inject
203 public MyClass(@PluginCanonicalWebUrl String url) {
204 this.url = url;
205 }
206
207 [...]
208}
209----
210
211The URL is composed of the server's canonical web URL and the plugin's
212name, i.e. `http://review.example.com:8080/plugin-name`.
213
214The canonical web URL may be injected into any .jar plugin regardless of
215whether or not the plugin provides an HTTP servlet.
216
Edwin Kempinf7295742012-07-16 15:03:46 +0200217[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700218Reload Method
219~~~~~~~~~~~~~
220
221If a plugin holds an exclusive resource that must be released before
222loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200223acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700224to be `restart`. Otherwise the preferred method of `reload` will
225be used, as it enables the server to hot-patch an updated plugin
226with no down time.
227
228====
229 Gerrit-ReloadMode: restart
230====
231
232In either mode ('restart' or 'reload') any plugin or extension can
233be updated without restarting the Gerrit server. The difference is
234how Gerrit handles the upgrade:
235
236restart::
237 The old plugin is completely stopped. All registrations of SSH
238 commands and HTTP servlets are removed. All registrations of any
239 extension points are removed. All registered LifecycleListeners
240 have their `stop()` method invoked in reverse order. The new
241 plugin is started, and registrations are made from the new
242 plugin. There is a brief window where neither the old nor the
243 new plugin is connected to the server. This means SSH commands
244 and HTTP servlets will return not found errors, and the plugin
245 will not be notified of events that occurred during the restart.
246
247reload::
248 The new plugin is started. Its LifecycleListeners are permitted
249 to perform their `start()` methods. All SSH and HTTP registrations
250 are atomically swapped out from the old plugin to the new plugin,
251 ensuring the server never returns a not found error. All extension
252 point listeners are atomically swapped out from the old plugin to
253 the new plugin, ensuring no events are missed (however some events
254 may still route to the old plugin if the swap wasn't complete yet).
255 The old plugin is stopped.
256
Edwin Kempinf7295742012-07-16 15:03:46 +0200257To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
258command can be used.
259
Luca Milanesio737285d2012-09-25 14:26:43 +0100260[[init_step]]
261Init step
262~~~~~~~~~
263
264Plugins can contribute their own "init step" during the Gerrit init
265wizard. This is useful for guiding the Gerrit administrator through
266the settings needed by the plugin to work propertly.
267
268For instance plugins to integrate Jira issues to Gerrit changes may
269contribute their own "init step" to allow configuring the Jira URL,
270credentials and possibly verify connectivity to validate them.
271
272====
273 Gerrit-InitStep: tld.example.project.MyInitStep
274====
275
276MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900277and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100278and accessing / changing configuration settings using Section.Factory.
279
280In addition to the standard Gerrit init injections, plugins receive
281the @PluginName String injection containing their own plugin name.
282
Edwin Kempind4cfac12013-11-27 11:22:34 +0100283During their initialization plugins may get access to the
284`project.config` file of the `All-Projects` project and they are able
285to store configuration parameters in it. For this a plugin `InitStep`
286can get `com.google.gerrit.pgm.init.AllProjectsConfig` injected:
287
288[source,java]
289----
290 public class MyInitStep implements InitStep {
291 private final String pluginName;
292 private final ConsoleUI ui;
293 private final AllProjectsConfig allProjectsConfig;
294
295 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
296 AllProjectsConfig allProjectsConfig) {
297 this.pluginName = pluginName;
298 this.ui = ui;
299 this.allProjectsConfig = allProjectsConfig;
300 }
301
302 @Override
303 public void run() throws Exception {
304 ui.message("\n");
305 ui.header(pluginName + " Integration");
306 boolean enabled = ui.yesno(true, "By default enabled for all projects");
307 Config cfg = allProjectsConfig.load();
308 if (enabled) {
309 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
310 } else {
311 cfg.unset("plugin", pluginName, "enabled");
312 }
313 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
314 }
315 }
316----
317
Luca Milanesio737285d2012-09-25 14:26:43 +0100318Bear in mind that the Plugin's InitStep class will be loaded but
319the standard Gerrit runtime environment is not available and the plugin's
320own Guice modules were not initialized.
321This means the InitStep for a plugin is not executed in the same way that
322the plugin executes within the server, and may mean a plugin author cannot
323trivially reuse runtime code during init.
324
325For instance a plugin that wants to verify connectivity may need to statically
326call the constructor of their connection class, passing in values obtained
327from the Section.Factory rather than from an injected Config object.
328
David Pursehoused128c892013-10-22 21:52:21 +0900329Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
330the extraction of the plugins embedded in the distribution .war file into
331`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
332
333A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
334runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100335
David Pursehouse68153d72013-09-04 10:09:17 +0900336[source,java]
337----
338public class MyInitStep implements InitStep {
339 private final ConsoleUI ui;
340 private final Section.Factory sections;
341 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100342
David Pursehouse68153d72013-09-04 10:09:17 +0900343 @Inject
344 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
345 this.ui = ui;
346 this.sections = sections;
347 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100348 }
David Pursehouse68153d72013-09-04 10:09:17 +0900349
350 @Override
351 public void run() throws Exception {
352 ui.header("\nMy plugin");
353
354 Section mySection = getSection("myplugin", null);
355 mySection.string("Link name", "linkname", "MyLink");
356 }
357}
358----
Luca Milanesio737285d2012-09-25 14:26:43 +0100359
Edwin Kempinf5a77332012-07-18 11:17:53 +0200360[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700361Classpath
362---------
363
364Each plugin is loaded into its own ClassLoader, isolating plugins
365from each other. A plugin or extension inherits the Java runtime
366and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
367from the hosting server.
368
369Plugins are loaded from a single JAR file. If a plugin needs
370additional libraries, it must include those dependencies within
371its own JAR. Plugins built using Maven may be able to use the
372link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
373to package additional dependencies. Relocating (or renaming) classes
374should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700375
Edwin Kempin98202662013-09-18 16:03:03 +0200376[[events]]
377Listening to Events
378-------------------
379
380Certain operations in Gerrit trigger events. Plugins may receive
381notifications of these events by implementing the corresponding
382listeners.
383
Edwin Kempin64059f52013-10-31 13:49:25 +0100384* `com.google.gerrit.common.ChangeListener`:
385+
386Allows to listen to change events. These are the same
387link:cmd-stream-events.html#events[events] that are also streamed by
388the link:cmd-stream-events.html[gerrit stream-events] command.
389
Edwin Kempin98202662013-09-18 16:03:03 +0200390* `com.google.gerrit.extensions.events.LifecycleListener`:
391+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100392Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200393
394* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
395+
396Project creation
397
398* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
399+
400Project deletion
401
Edwin Kempinb27c9392013-11-19 13:12:43 +0100402* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
403+
404Update of HEAD on a project
405
Yang Zhenhui2659d422013-07-30 16:59:58 +0800406[[stream-events]]
407Sending Events to the Events Stream
408-----------------------------------
409
410Plugins may send events to the events stream where consumers of
411Gerrit's `stream-events` ssh command will receive them.
412
413To send an event, the plugin must invoke one of the `postEvent`
414methods in the `ChangeHookRunner` class, passing an instance of
415its own custom event class derived from `ChangeEvent`.
416
Edwin Kempinf5a77332012-07-18 11:17:53 +0200417[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700418SSH Commands
419------------
420
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700421Plugins may provide commands that can be accessed through the SSH
422interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700423
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700424Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700425
David Pursehouse68153d72013-09-04 10:09:17 +0900426[source,java]
427----
428import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100429import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700430
David Ostrovskyb7d97752013-11-09 05:23:26 +0100431@CommandMetaData(name="print", descr="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900432class PrintHello extends SshCommand {
433 protected abstract void run() {
434 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700435 }
David Pursehouse68153d72013-09-04 10:09:17 +0900436}
437----
Nasser Grainawie033b262012-05-09 17:54:21 -0700438
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700439If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200440use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700441
David Pursehouse68153d72013-09-04 10:09:17 +0900442[source,java]
443----
444import com.google.gerrit.extensions.annotations.Export;
445import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700446
David Pursehouse68153d72013-09-04 10:09:17 +0900447@Export("print")
448class PrintHello extends SshCommand {
449 protected abstract void run() {
450 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700451 }
David Pursehouse68153d72013-09-04 10:09:17 +0900452}
453----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700454
455If explicit registration is being used, a Guice module must be
456supplied to register the SSH command and declared in the manifest
457with the `Gerrit-SshModule` attribute:
458
David Pursehouse68153d72013-09-04 10:09:17 +0900459[source,java]
460----
461import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700462
David Pursehouse68153d72013-09-04 10:09:17 +0900463class MyCommands extends PluginCommandModule {
464 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100465 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700466 }
David Pursehouse68153d72013-09-04 10:09:17 +0900467}
468----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700469
470For a plugin installed as name `helloworld`, the command implemented
471by PrintHello class will be available to users as:
472
473----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600474$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700475----
476
David Ostrovskye3172b32013-10-13 14:19:13 +0200477Multiple SSH commands can be bound to the same implementation class. For
478example a Gerrit Shell plugin can bind different shell commands to the same
479implementation class:
480
481[source,java]
482----
483public class SshShellModule extends PluginCommandModule {
484 @Override
485 protected void configureCommands() {
486 command("ls").to(ShellCommand.class);
487 command("ps").to(ShellCommand.class);
488 [...]
489 }
490}
491----
492
493With the possible implementation:
494
495[source,java]
496----
497public class ShellCommand extends SshCommand {
498 @Override
499 protected void run() throws UnloggedFailure {
500 String cmd = getName().substring(getPluginName().length() + 1);
501 ProcessBuilder proc = new ProcessBuilder(cmd);
502 Process cmd = proc.start();
503 [...]
504 }
505}
506----
507
508And the call:
509
510----
511$ ssh -p 29418 review.example.com shell ls
512$ ssh -p 29418 review.example.com shell ps
513----
514
David Ostrovskyb7d97752013-11-09 05:23:26 +0100515Single command plugins are also supported. In this scenario plugin binds
516SSH command to its own name. `SshModule` must inherit from
517`SingleCommandPluginModule` class:
518
519[source,java]
520----
521public class SshModule extends SingleCommandPluginModule {
522 @Override
523 protected void configure(LinkedBindingBuilder<Command> b) {
524 b.to(ShellCommand.class);
525 }
526}
527----
528
529If the plugin above is deployed under sh.jar file in `$site/plugins`
530directory, generic commands can be called without specifing the
531actual SSH command. Note in the example below, that the called commands
532`ls` and `ps` was not explicitly bound:
533
534----
535$ ssh -p 29418 review.example.com sh ls
536$ ssh -p 29418 review.example.com sh ps
537----
538
Edwin Kempin78ca0942013-10-30 11:24:06 +0100539[[simple-configuration]]
540Simple Configuration in `gerrit.config`
541---------------------------------------
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200542
543In Gerrit, global configuration is stored in the `gerrit.config` file.
544If a plugin needs global configuration, this configuration should be
545stored in a `plugin` subsection in the `gerrit.config` file.
546
Edwin Kempinc9b68602013-10-30 09:32:43 +0100547This approach of storing the plugin configuration is only suitable for
548plugins that have a simple configuration that only consists of
549key-value pairs. With this approach it is not possible to have
550subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100551configuration need to store their configuration in their
552link:#configuration[own configuration file] where they can make use of
553subsections. On the other hand storing the plugin configuration in a
554'plugin' subsection in the `gerrit.config` file has the advantage that
555administrators have all configuration parameters in one file, instead
556of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100557
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200558To avoid conflicts with other plugins, it is recommended that plugins
559only use the `plugin` subsection with their own name. For example the
560`helloworld` plugin should store its configuration in the
561`plugin.helloworld` subsection:
562
563----
564[plugin "helloworld"]
565 language = Latin
566----
567
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200568Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200569plugin can easily access its configuration and there is no need for a
570plugin to parse the `gerrit.config` file on its own:
571
572[source,java]
573----
David Pursehouse529ec252013-09-27 13:45:14 +0900574@Inject
575private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200576
David Pursehoused128c892013-10-22 21:52:21 +0900577[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200578
Edwin Kempin122622d2013-10-29 16:45:44 +0100579String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900580 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200581----
582
Edwin Kempin78ca0942013-10-30 11:24:06 +0100583[[configuration]]
584Configuration in own config file
585--------------------------------
586
587Plugins can store their configuration in an own configuration file.
588This makes sense if the plugin configuration is rather complex and
589requires the usage of subsections. Plugins that have a simple
590key-value pair configuration can store their configuration in a
591link:#simple-configuration[`plugin` subsection of the `gerrit.config`
592file].
593
594The plugin configuration file must be named after the plugin and must
595be located in the `etc` folder of the review site. For example a
596configuration file for a `default-reviewer` plugin could look like
597this:
598
599.$site_path/etc/default-reviewer.config
600----
601[branch "refs/heads/master"]
602 reviewer = Project Owners
603 reviewer = john.doe@example.com
604[match "file:^.*\.txt"]
605 reviewer = My Info Developers
606----
607
608Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
609plugin can easily access its configuration:
610
611[source,java]
612----
613@Inject
614private com.google.gerrit.server.config.PluginConfigFactory cfg;
615
616[...]
617
618String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
619 .getStringList("branch", "refs/heads/master", "reviewer");
620----
621
622The plugin configuration is loaded only once and is then cached.
623Similar to changes in 'gerrit.config', changes to the plugin
624configuration file will only become effective after a Gerrit restart.
625
Edwin Kempin705f2842013-10-30 14:25:31 +0100626[[simple-project-specific-configuration]]
627Simple Project Specific Configuration in `project.config`
628---------------------------------------------------------
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200629
630In Gerrit, project specific configuration is stored in the project's
631`project.config` file on the `refs/meta/config` branch. If a plugin
632needs configuration on project level (e.g. to enable its functionality
633only for certain projects), this configuration should be stored in a
634`plugin` subsection in the project's `project.config` file.
635
Edwin Kempinc9b68602013-10-30 09:32:43 +0100636This approach of storing the plugin configuration is only suitable for
637plugins that have a simple configuration that only consists of
638key-value pairs. With this approach it is not possible to have
639subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100640configuration need to store their configuration in their
641link:#project-specific-configuration[own configuration file] where they
642can make use of subsections. On the other hand storing the plugin
643configuration in a 'plugin' subsection in the `project.config` file has
644the advantage that project owners have all configuration parameters in
645one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100646
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200647To avoid conflicts with other plugins, it is recommended that plugins
648only use the `plugin` subsection with their own name. For example the
649`helloworld` plugin should store its configuration in the
650`plugin.helloworld` subsection:
651
652----
653 [plugin "helloworld"]
654 enabled = true
655----
656
657Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
658plugin can easily access its project specific configuration and there
659is no need for a plugin to parse the `project.config` file on its own:
660
661[source,java]
662----
David Pursehouse529ec252013-09-27 13:45:14 +0900663@Inject
664private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200665
David Pursehoused128c892013-10-22 21:52:21 +0900666[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200667
Edwin Kempin122622d2013-10-29 16:45:44 +0100668boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900669 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200670----
671
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200672It is also possible to get missing configuration parameters inherited
673from the parent projects:
674
675[source,java]
676----
David Pursehouse529ec252013-09-27 13:45:14 +0900677@Inject
678private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200679
David Pursehoused128c892013-10-22 21:52:21 +0900680[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200681
Edwin Kempin122622d2013-10-29 16:45:44 +0100682boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900683 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200684----
685
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200686Project owners can edit the project configuration by fetching the
687`refs/meta/config` branch, editing the `project.config` file and
688pushing the commit back.
689
Edwin Kempin705f2842013-10-30 14:25:31 +0100690[[project-specific-configuration]]
691Project Specific Configuration in own config file
692-------------------------------------------------
693
694Plugins can store their project specific configuration in an own
695configuration file in the projects `refs/meta/config` branch.
696This makes sense if the plugins project specific configuration is
697rather complex and requires the usage of subsections. Plugins that
698have a simple key-value pair configuration can store their project
699specific configuration in a link:#simple-project-specific-configuration[
700`plugin` subsection of the `project.config` file].
701
702The plugin configuration file in the `refs/meta/config` branch must be
703named after the plugin. For example a configuration file for a
704`default-reviewer` plugin could look like this:
705
706.default-reviewer.config
707----
708[branch "refs/heads/master"]
709 reviewer = Project Owners
710 reviewer = john.doe@example.com
711[match "file:^.*\.txt"]
712 reviewer = My Info Developers
713----
714
715Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
716plugin can easily access its project specific configuration:
717
718[source,java]
719----
720@Inject
721private com.google.gerrit.server.config.PluginConfigFactory cfg;
722
723[...]
724
725String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
726 .getStringList("branch", "refs/heads/master", "reviewer");
727----
728
Edwin Kempin762da382013-10-30 14:50:01 +0100729It is also possible to get missing configuration parameters inherited
730from the parent projects:
731
732[source,java]
733----
734@Inject
735private com.google.gerrit.server.config.PluginConfigFactory cfg;
736
737[...]
738
739String[] reviewers = cfg.getFromPluginConfigWithInheritance(project, "default-reviewer")
740 .getStringList("branch", "refs/heads/master", "reviewer");
741----
742
Edwin Kempin705f2842013-10-30 14:25:31 +0100743Project owners can edit the project configuration by fetching the
744`refs/meta/config` branch, editing the `<plugin-name>.config` file and
745pushing the commit back.
746
David Ostrovsky7066cc02013-06-15 14:46:23 +0200747[[capabilities]]
748Plugin Owned Capabilities
749-------------------------
750
751Plugins may provide their own capabilities and restrict usage of SSH
752commands to the users who are granted those capabilities.
753
754Plugins define the capabilities by overriding the `CapabilityDefinition`
755abstract class:
756
David Pursehouse68153d72013-09-04 10:09:17 +0900757[source,java]
758----
759public class PrintHelloCapability extends CapabilityDefinition {
760 @Override
761 public String getDescription() {
762 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200763 }
David Pursehouse68153d72013-09-04 10:09:17 +0900764}
765----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200766
David Ostrovskyf86bae52013-09-01 09:10:39 +0200767If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200768use auto-registration by providing an `@Export` annotation:
769
David Pursehouse68153d72013-09-04 10:09:17 +0900770[source,java]
771----
772@Export("printHello")
773public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +0900774 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900775}
776----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200777
778Otherwise the capability must be bound in a plugin module:
779
David Pursehouse68153d72013-09-04 10:09:17 +0900780[source,java]
781----
782public class HelloWorldModule extends AbstractModule {
783 @Override
784 protected void configure() {
785 bind(CapabilityDefinition.class)
786 .annotatedWith(Exports.named("printHello"))
787 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200788 }
David Pursehouse68153d72013-09-04 10:09:17 +0900789}
790----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200791
792With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200793usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200794this capability in the usual way, using the `RequiresCapability` annotation:
795
David Pursehouse68153d72013-09-04 10:09:17 +0900796[source,java]
797----
798@RequiresCapability("printHello")
799@CommandMetaData(name="print", description="Print greeting in different languages")
800public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +0900801 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900802}
803----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200804
David Ostrovskyf86bae52013-09-01 09:10:39 +0200805Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200806
David Pursehouse68153d72013-09-04 10:09:17 +0900807[source,java]
808----
809@RequiresCapability("printHello")
810public class SayHelloAction extends UiAction<RevisionResource>
811 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +0900812 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900813}
814----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200815
816Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900817capabilities and core capabilities. Per default the scope of the
818`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
819
David Ostrovsky7066cc02013-06-15 14:46:23 +0200820* when `@RequiresCapability` is used within a plugin the scope of the
821capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900822
David Ostrovsky7066cc02013-06-15 14:46:23 +0200823* If `@RequiresCapability` is used within the core Gerrit Code Review server
824(and thus is outside of a plugin) the scope is the core server and will use
825the `GlobalCapability` known to Gerrit Code Review server.
826
827If a plugin needs to use a core capability name (e.g. "administrateServer")
828this can be specified by setting `scope = CapabilityScope.CORE`:
829
David Pursehouse68153d72013-09-04 10:09:17 +0900830[source,java]
831----
832@RequiresCapability(value = "administrateServer", scope =
833 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +0900834 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900835----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200836
David Ostrovskyf86bae52013-09-01 09:10:39 +0200837[[ui_extension]]
838UI Extension
839------------
840
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100841Plugins can contribute UI actions on core Gerrit pages. This is useful
842for workflow customization or exposing plugin functionality through the
843UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200844
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100845For instance a plugin to integrate Jira with Gerrit changes may
846contribute a "File bug" button to allow filing a bug from the change
847page or plugins to integrate continuous integration systems may
848contribute a "Schedule" button to allow a CI build to be scheduled
849manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200850
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100851Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200852
853* Change screen
854* Project info screen
855
856Plugins contribute UI actions by implementing the `UiAction` interface:
857
David Pursehouse68153d72013-09-04 10:09:17 +0900858[source,java]
859----
860@RequiresCapability("printHello")
861class HelloWorldAction implements UiAction<RevisionResource>,
862 RestModifyView<RevisionResource, HelloWorldAction.Input> {
863 static class Input {
864 boolean french;
865 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200866 }
David Pursehouse68153d72013-09-04 10:09:17 +0900867
868 private Provider<CurrentUser> user;
869
870 @Inject
871 HelloWorldAction(Provider<CurrentUser> user) {
872 this.user = user;
873 }
874
875 @Override
876 public String apply(RevisionResource rev, Input input) {
877 final String greeting = input.french
878 ? "Bonjour"
879 : "Hello";
880 return String.format("%s %s from change %s, patch set %d!",
881 greeting,
882 Strings.isNullOrEmpty(input.message)
883 ? Objects.firstNonNull(user.get().getUserName(), "world")
884 : input.message,
885 rev.getChange().getId().toString(),
886 rev.getPatchSet().getPatchSetId());
887 }
888
889 @Override
890 public Description getDescription(
891 RevisionResource resource) {
892 return new Description()
893 .setLabel("Say hello")
894 .setTitle("Say hello in different languages");
895 }
896}
897----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200898
David Ostrovsky450eefe2013-10-21 21:18:11 +0200899Sometimes plugins may want to be able to change the state of a patch set or
900change in the `UiAction.apply()` method and reflect these changes on the core
901UI. For example a buildbot plugin which exposes a 'Schedule' button on the
902patch set panel may want to disable that button after the build was scheduled
903and update the tooltip of that button. But because of Gerrit's caching
904strategy the following must be taken into consideration.
905
906The browser is allowed to cache the `UiAction` information until something on
907the change is modified. More accurately the change row needs to be modified in
908the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
909the +refs/meta/config+ of the project or any parents needs to change to a new
910SHA-1. The ETag SHA-1 computation code can be found in the
911`ChangeResource.getETag()` method.
912
David Pursehoused128c892013-10-22 21:52:21 +0900913The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +0200914
915[source,java]
916----
917@Override
918public Object apply(RevisionResource rcrs, Input in) {
919 // schedule a build
920 [...]
921 // update change
922 ReviewDb db = dbProvider.get();
923 db.changes().beginTransaction(change.getId());
924 try {
925 change = db.changes().atomicUpdate(
926 change.getId(),
927 new AtomicUpdate<Change>() {
928 @Override
929 public Change update(Change change) {
930 ChangeUtil.updated(change);
931 return change;
932 }
933 });
934 db.commit();
935 } finally {
936 db.rollback();
937 }
David Pursehoused128c892013-10-22 21:52:21 +0900938 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +0200939}
940----
941
David Ostrovskyf86bae52013-09-01 09:10:39 +0200942`UiAction` must be bound in a plugin module:
943
David Pursehouse68153d72013-09-04 10:09:17 +0900944[source,java]
945----
946public class Module extends AbstractModule {
947 @Override
948 protected void configure() {
949 install(new RestApiModule() {
950 @Override
951 protected void configure() {
952 post(REVISION_KIND, "say-hello")
953 .to(HelloWorldAction.class);
954 }
955 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200956 }
David Pursehouse68153d72013-09-04 10:09:17 +0900957}
958----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200959
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100960The module above must be declared in the `pom.xml` for Maven driven
961plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200962
David Pursehouse68153d72013-09-04 10:09:17 +0900963[source,xml]
964----
965<manifestEntries>
966 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
967</manifestEntries>
968----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200969
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100970or in the `BUCK` configuration file for Buck driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200971
David Pursehouse68153d72013-09-04 10:09:17 +0900972[source,python]
973----
974manifest_entries = [
975 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
976]
977----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200978
979In some use cases more user input must be gathered, for that `UiAction` can be
980combined with the JavaScript API. This would display a small popup near the
981activation button to gather additional input from the user. The JS file is
982typically put in the `static` folder within the plugin's directory:
983
David Pursehouse68153d72013-09-04 10:09:17 +0900984[source,javascript]
985----
986Gerrit.install(function(self) {
987 function onSayHello(c) {
988 var f = c.textfield();
989 var t = c.checkbox();
990 var b = c.button('Say hello', {onclick: function(){
991 c.call(
992 {message: f.value, french: t.checked},
993 function(r) {
994 c.hide();
995 window.alert(r);
996 c.refresh();
997 });
998 }});
999 c.popup(c.div(
1000 c.prependLabel('Greeting message', f),
1001 c.br(),
1002 c.label(t, 'french'),
1003 c.br(),
1004 b));
1005 f.focus();
1006 }
1007 self.onAction('revision', 'say-hello', onSayHello);
1008});
1009----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001010
1011The JS module must be exposed as a `WebUiPlugin` and bound as
1012an HTTP Module:
1013
David Pursehouse68153d72013-09-04 10:09:17 +09001014[source,java]
1015----
1016public class HttpModule extends HttpPluginModule {
1017 @Override
1018 protected void configureServlets() {
1019 DynamicSet.bind(binder(), WebUiPlugin.class)
1020 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001021 }
David Pursehouse68153d72013-09-04 10:09:17 +09001022}
1023----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001024
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001025The HTTP module above must be declared in the `pom.xml` for Maven
1026driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001027
David Pursehouse68153d72013-09-04 10:09:17 +09001028[source,xml]
1029----
1030<manifestEntries>
1031 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1032</manifestEntries>
1033----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001034
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001035or in the `BUCK` configuration file for Buck driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001036
David Pursehouse68153d72013-09-04 10:09:17 +09001037[source,python]
1038----
1039manifest_entries = [
1040 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1041]
1042----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001043
1044If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1045capability check is done during the `UiAction` gathering, so the plugin author
1046doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1047case.
1048
1049The following prerequisities must be met, to satisfy the capability check:
1050
1051* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001052* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001053* user is a member of a group which has the required capability
1054
1055The `apply` method is called when the button is clicked. If `UiAction` is
1056combined with JavaScript API (its own JavaScript function is provided),
1057then a popup dialog is normally opened to gather additional user input.
1058A new button is placed on the popup dialog to actually send the request.
1059
1060Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1061can be accessed from any REST client, i. e.:
1062
1063====
1064 curl -X POST -H "Content-Type: application/json" \
1065 -d '{message: "François", french: true}' \
1066 --digest --user joe:secret \
1067 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1068 "Bonjour François from change 1, patch set 1!"
1069====
1070
David Pursehouse42245822013-09-24 09:48:20 +09001071A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001072particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001073
1074[source,java]
1075----
1076public class Module extends AbstractModule {
1077 @Override
1078 protected void configure() {
1079 install(new RestApiModule() {
1080 @Override
1081 protected void configure() {
1082 delete(PROJECT_KIND)
1083 .to(DeleteProject.class);
1084 }
1085 });
1086 }
1087}
1088----
1089
David Pursehouse42245822013-09-24 09:48:20 +09001090For a `UiAction` bound this way, a JS API function can be provided.
1091
1092Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001093can be bound per resource without view name. To define a JS function
1094for the `UiAction`, "/" must be used as the name:
1095
1096[source,javascript]
1097----
1098Gerrit.install(function(self) {
1099 function onDeleteProject(c) {
1100 [...]
1101 }
1102 self.onAction('project', '/', onDeleteProject);
1103});
1104----
1105
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001106[[top-menu-extensions]]
1107Top Menu Extensions
1108-------------------
1109
1110Plugins can contribute items to Gerrit's top menu.
1111
1112A single top menu extension can have multiple elements and will be put as
1113the last element in Gerrit's top menu.
1114
1115Plugins define the top menu entries by implementing `TopMenu` interface:
1116
1117[source,java]
1118----
1119public class MyTopMenuExtension implements TopMenu {
1120
1121 @Override
1122 public List<MenuEntry> getEntries() {
1123 return Lists.newArrayList(
1124 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1125 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1126 }
1127}
1128----
1129
Edwin Kempin77f23242013-09-30 14:53:20 +02001130Plugins can also add additional menu items to Gerrit's top menu entries
1131by defining a `MenuEntry` that has the same name as a Gerrit top menu
1132entry:
1133
1134[source,java]
1135----
1136public class MyTopMenuExtension implements TopMenu {
1137
1138 @Override
1139 public List<MenuEntry> getEntries() {
1140 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001141 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001142 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1143 }
1144}
1145----
1146
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001147If no Guice modules are declared in the manifest, the top menu extension may use
1148auto-registration by providing an `@Listen` annotation:
1149
1150[source,java]
1151----
1152@Listen
1153public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001154 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001155}
1156----
1157
Luca Milanesiocb230402013-10-11 08:49:56 +01001158Otherwise the top menu extension must be bound in the plugin module used
1159for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001160
1161[source,java]
1162----
Luca Milanesiocb230402013-10-11 08:49:56 +01001163package com.googlesource.gerrit.plugins.helloworld;
1164
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001165public class HelloWorldModule extends AbstractModule {
1166 @Override
1167 protected void configure() {
1168 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1169 }
1170}
1171----
1172
Luca Milanesiocb230402013-10-11 08:49:56 +01001173[source,manifest]
1174----
1175Gerrit-ApiType: plugin
1176Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1177----
1178
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001179It is also possible to show some menu entries only if the user has a
1180certain capability:
1181
1182[source,java]
1183----
1184public class MyTopMenuExtension implements TopMenu {
1185 private final String pluginName;
1186 private final Provider<CurrentUser> userProvider;
1187 private final List<MenuEntry> menuEntries;
1188
1189 @Inject
1190 public MyTopMenuExtension(@PluginName String pluginName,
1191 Provider<CurrentUser> userProvider) {
1192 this.pluginName = pluginName;
1193 this.userProvider = userProvider;
1194 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1195
1196 // add menu entry that is only visible to users with a certain capability
1197 if (canSeeMenuEntry()) {
1198 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1199 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1200 }
1201
1202 // add menu entry that is visible to all users (even anonymous users)
1203 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1204 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1205 }
1206
1207 private boolean canSeeMenuEntry() {
1208 if (userProvider.get().isIdentifiedUser()) {
1209 CapabilityControl ctl = userProvider.get().getCapabilities();
1210 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1211 || ctl.canAdministrateServer();
1212 } else {
1213 return false;
1214 }
1215 }
1216
1217 @Override
1218 public List<MenuEntry> getEntries() {
1219 return menuEntries;
1220 }
1221}
1222----
1223
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001224[[gwt_ui_extension]]
1225GWT UI Extension
1226----------------
1227Plugins can extend the Gerrit UI with own GWT code.
1228
1229The Maven archetype 'gerrit-plugin-gwt-archetype' can be used to
1230generate a GWT plugin skeleton. How to use the Maven plugin archetypes
1231is described in the link:#getting-started[Getting started] section.
1232
1233The generated GWT plugin has a link:#top-menu-extensions[top menu] that
1234opens a GWT dialog box when the user clicks on it.
1235
Edwin Kempinb74daa92013-11-11 11:28:16 +01001236In addition to the Gerrit-Plugin API a GWT plugin depends on
1237`gerrit-plugin-gwtui`. This dependency must be specified in the
1238`pom.xml`:
1239
1240[source,xml]
1241----
1242<dependency>
1243 <groupId>com.google.gerrit</groupId>
1244 <artifactId>gerrit-plugin-gwtui</artifactId>
1245 <version>${Gerrit-ApiVersion}</version>
1246</dependency>
1247----
1248
1249A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1250that bundles together all the configuration settings of the GWT plugin:
1251
1252[source,xml]
1253----
1254<?xml version="1.0" encoding="UTF-8"?>
1255<module rename-to="hello_gwt_plugin">
1256 <!-- Inherit the core Web Toolkit stuff. -->
1257 <inherits name="com.google.gwt.user.User"/>
1258 <!-- Other module inherits -->
1259 <inherits name="com.google.gerrit.Plugin"/>
1260 <inherits name="com.google.gwt.http.HTTP"/>
1261 <!-- Using GWT built-in themes adds a number of static -->
1262 <!-- resources to the plugin. No theme inherits lines were -->
1263 <!-- added in order to make this plugin as simple as possible -->
1264 <!-- Specify the app entry point class. -->
1265 <entry-point class="${package}.client.HelloPlugin"/>
1266 <stylesheet src="hello.css"/>
1267</module>
1268----
1269
1270The GWT module must inherit `com.google.gerrit.Plugin` and
1271`com.google.gwt.http.HTTP`.
1272
1273To register the GWT module a `GwtPlugin` needs to be bound.
1274
1275If no Guice modules are declared in the manifest, the GWT plugin may
1276use auto-registration by using the `@Listen` annotation:
1277
1278[source,java]
1279----
1280@Listen
1281public class MyExtension extends GwtPlugin {
1282 public MyExtension() {
1283 super("hello_gwt_plugin");
1284 }
1285}
1286----
1287
1288Otherwise the binding must be done in an `HttpModule`:
1289
1290[source,java]
1291----
1292public class HttpModule extends HttpPluginModule {
1293
1294 @Override
1295 protected void configureServlets() {
1296 DynamicSet.bind(binder(), WebUiPlugin.class)
1297 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1298 }
1299}
1300----
1301
1302The HTTP module above must be declared in the `pom.xml` for Maven
1303driven plugins:
1304
1305[source,xml]
1306----
1307<manifestEntries>
1308 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1309</manifestEntries>
1310----
1311
1312It is important that the module name that is provided to the
1313`GwtPlugin` matches the GWT module contained in the plugin. The name
1314of the GWT module can be explicitly set in the GWT module file by
1315specifying the `rename-to` attribute on the module.
1316
1317[source,xml]
1318----
1319<module rename-to="hello_gwt_plugin">
1320----
1321
1322The actual GWT code must be implemented in a class that extends
1323`com.google.gerrit.plugin.client.Plugin`:
1324
1325[source,java]
1326----
1327public class HelloPlugin extends Plugin {
1328
1329 @Override
1330 public void onModuleLoad() {
1331 // Create the dialog box
1332 final DialogBox dialogBox = new DialogBox();
1333
1334 // The content of the dialog comes from a User specified Preference
1335 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1336 dialogBox.setAnimationEnabled(true);
1337 Button closeButton = new Button("Close");
1338 VerticalPanel dialogVPanel = new VerticalPanel();
1339 dialogVPanel.setWidth("100%");
1340 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1341 dialogVPanel.add(closeButton);
1342
1343 closeButton.addClickHandler(new ClickHandler() {
1344 public void onClick(ClickEvent event) {
1345 dialogBox.hide();
1346 }
1347 });
1348
1349 // Set the contents of the Widget
1350 dialogBox.setWidget(dialogVPanel);
1351
1352 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1353 rootPanel.getElement().removeAttribute("href");
1354 rootPanel.addDomHandler(new ClickHandler() {
1355 @Override
1356 public void onClick(ClickEvent event) {
1357 dialogBox.center();
1358 dialogBox.show();
1359 }
1360 }, ClickEvent.getType());
1361 }
1362}
1363----
1364
1365This class must be set as entry point in the GWT module:
1366
1367[source,xml]
1368----
1369<entry-point class="${package}.client.HelloPlugin"/>
1370----
1371
1372In addition this class must be defined as module in the `pom.xml` for the
1373`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1374must be set to `${project.build.directory}/classes/static`:
1375
1376[source,xml]
1377----
1378<plugin>
1379 <groupId>org.codehaus.mojo</groupId>
1380 <artifactId>gwt-maven-plugin</artifactId>
1381 <version>2.5.1</version>
1382 <configuration>
1383 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1384 <disableClassMetadata>true</disableClassMetadata>
1385 <disableCastChecking>true</disableCastChecking>
1386 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1387 </configuration>
1388 <executions>
1389 <execution>
1390 <goals>
1391 <goal>compile</goal>
1392 </goals>
1393 </execution>
1394 </executions>
1395</plugin>
1396----
1397
1398To attach a GWT widget defined by the plugin to the Gerrit core UI
1399`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1400Gerrit core widgets:
1401
1402[source,java]
1403----
1404RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1405rootPanel.getElement().removeAttribute("href");
1406rootPanel.addDomHandler(new ClickHandler() {
1407 @Override
1408 public void onClick(ClickEvent event) {
1409 dialogBox.center();
1410 dialogBox.show();
1411 }
1412}, ClickEvent.getType());
1413----
1414
1415GWT plugins can come with their own css file. This css file must have a
1416unique name and must be registered in the GWT module:
1417
1418[source,xml]
1419----
1420<stylesheet src="hello.css"/>
1421----
1422
Edwin Kempin2570b102013-11-11 11:44:50 +01001423If a GWT plugin wants to invoke the Gerrit REST API it can use
1424`com.google.gerrit.plugin.client.rpc.RestApi` to contruct the URL
1425path and to trigger the REST calls.
1426
1427Example for invoking a Gerrit core REST endpoint:
1428
1429[source,java]
1430----
1431new RestApi("projects").id(projectName).view("description")
1432 .put("new description", new AsyncCallback<JavaScriptObject>() {
1433
1434 @Override
1435 public void onSuccess(JavaScriptObject result) {
1436 // TODO
1437 }
1438
1439 @Override
1440 public void onFailure(Throwable caught) {
1441 // never invoked
1442 }
1443});
1444----
1445
1446Example for invoking a REST endpoint defined by a plugin:
1447
1448[source,java]
1449----
1450new RestApi("projects").id(projectName).view("myplugin", "myview")
1451 .get(new AsyncCallback<JavaScriptObject>() {
1452
1453 @Override
1454 public void onSuccess(JavaScriptObject result) {
1455 // TODO
1456 }
1457
1458 @Override
1459 public void onFailure(Throwable caught) {
1460 // never invoked
1461 }
1462});
1463----
1464
1465The `onFailure(Throwable)` of the provided callback is never invoked.
1466If an error occurs, it is shown in an error dialog.
1467
1468In order to be able to do REST calls the GWT module must inherit
1469`com.google.gwt.json.JSON`:
1470
1471[source,xml]
1472----
1473<inherits name="com.google.gwt.json.JSON"/>
1474----
1475
Edwin Kempinf5a77332012-07-18 11:17:53 +02001476[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001477HTTP Servlets
1478-------------
1479
1480Plugins or extensions may register additional HTTP servlets, and
1481wrap them with HTTP filters.
1482
1483Servlets may use auto-registration to declare the URL they handle:
1484
David Pursehouse68153d72013-09-04 10:09:17 +09001485[source,java]
1486----
1487import com.google.gerrit.extensions.annotations.Export;
1488import com.google.inject.Singleton;
1489import javax.servlet.http.HttpServlet;
1490import javax.servlet.http.HttpServletRequest;
1491import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001492
David Pursehouse68153d72013-09-04 10:09:17 +09001493@Export("/print")
1494@Singleton
1495class HelloServlet extends HttpServlet {
1496 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
1497 res.setContentType("text/plain");
1498 res.setCharacterEncoding("UTF-8");
1499 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001500 }
David Pursehouse68153d72013-09-04 10:09:17 +09001501}
1502----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001503
Edwin Kempin8aa650f2012-07-18 11:25:48 +02001504The auto registration only works for standard servlet mappings like
1505`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
1506to register the HTTP servlets and declare it explicitly in the manifest
1507with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001508
David Pursehouse68153d72013-09-04 10:09:17 +09001509[source,java]
1510----
1511import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001512
David Pursehouse68153d72013-09-04 10:09:17 +09001513class MyWebUrls extends ServletModule {
1514 protected void configureServlets() {
1515 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001516 }
David Pursehouse68153d72013-09-04 10:09:17 +09001517}
1518----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001519
1520For a plugin installed as name `helloworld`, the servlet implemented
1521by HelloServlet class will be available to users as:
1522
1523----
1524$ curl http://review.example.com/plugins/helloworld/print
1525----
Nasser Grainawie033b262012-05-09 17:54:21 -07001526
Edwin Kempinf5a77332012-07-18 11:17:53 +02001527[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +02001528Data Directory
1529--------------
1530
1531Plugins can request a data directory with a `@PluginData` File
1532dependency. A data directory will be created automatically by the
1533server in `$site_path/data/$plugin_name` and passed to the plugin.
1534
1535Plugins can use this to store any data they want.
1536
David Pursehouse68153d72013-09-04 10:09:17 +09001537[source,java]
1538----
1539@Inject
1540MyType(@PluginData java.io.File myDir) {
1541 new FileInputStream(new File(myDir, "my.config"));
1542}
1543----
Edwin Kempin41f63912012-07-17 12:33:55 +02001544
Edwin Kempinea621482013-10-16 12:58:24 +02001545[[download-commands]]
1546Download Commands
1547-----------------
1548
1549Gerrit offers commands for downloading changes using different
1550download schemes (e.g. for downloading via different network
1551protocols). Plugins can contribute download schemes and download
1552commands by implementing
1553`com.google.gerrit.extensions.config.DownloadScheme` and
1554`com.google.gerrit.extensions.config.DownloadCommand`.
1555
1556The download schemes and download commands which are used most often
1557are provided by the Gerrit core plugin `download-commands`.
1558
Edwin Kempinf5a77332012-07-18 11:17:53 +02001559[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001560Documentation
1561-------------
1562
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001563If a plugin does not register a filter or servlet to handle URLs
1564`/Documentation/*` or `/static/*`, the core Gerrit server will
1565automatically export these resources over HTTP from the plugin JAR.
1566
David Pursehouse6853b5a2013-07-10 11:38:03 +09001567Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04001568available as `/plugins/helloworld/static/resource`. This prefix is
1569configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001570
David Pursehouse6853b5a2013-07-10 11:38:03 +09001571Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04001572will be available as `/plugins/helloworld/Documentation/resource`. This
1573prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
1574attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001575
1576Documentation may be written in
1577link:http://daringfireball.net/projects/markdown/[Markdown] style
1578if the file name ends with `.md`. Gerrit will automatically convert
1579Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07001580
Edwin Kempinf5a77332012-07-18 11:17:53 +02001581[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02001582Within the Markdown documentation files macros can be used that allow
1583to write documentation with reasonably accurate examples that adjust
1584automatically based on the installation.
1585
1586The following macros are supported:
1587
1588[width="40%",options="header"]
1589|===================================================
1590|Macro | Replacement
1591|@PLUGIN@ | name of the plugin
1592|@URL@ | Gerrit Web URL
1593|@SSH_HOST@ | SSH Host
1594|@SSH_PORT@ | SSH Port
1595|===================================================
1596
1597The macros will be replaced when the documentation files are rendered
1598from Markdown to HTML.
1599
1600Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
1601even if there is an expansion for `KEEP` in the future.
1602
Edwin Kempinf5a77332012-07-18 11:17:53 +02001603[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001604Automatic Index
1605~~~~~~~~~~~~~~~
1606
1607If a plugin does not handle its `/` URL itself, Gerrit will
1608redirect clients to the plugin's `/Documentation/index.html`.
1609Requests for `/Documentation/` (bare directory) will also redirect
1610to `/Documentation/index.html`.
1611
1612If neither resource `Documentation/index.html` or
1613`Documentation/index.md` exists in the plugin JAR, Gerrit will
1614automatically generate an index page for the plugin's documentation
1615tree by scanning every `*.md` and `*.html` file in the Documentation/
1616directory.
1617
1618For any discovered Markdown (`*.md`) file, Gerrit will parse the
1619header of the file and extract the first level one title. This
1620title text will be used as display text for a link to the HTML
1621version of the page.
1622
1623For any discovered HTML (`*.html`) file, Gerrit will use the name
1624of the file, minus the `*.html` extension, as the link text. Any
1625hyphens in the file name will be replaced with spaces.
1626
David Pursehouse6853b5a2013-07-10 11:38:03 +09001627If a discovered file is named `about.md` or `about.html`, its
1628content will be inserted in an 'About' section at the top of the
1629auto-generated index page. If both `about.md` and `about.html`
1630exist, only the first discovered file will be used.
1631
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001632If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09001633into a 'Commands' section of the generated index page.
1634
David Pursehousefe529152013-08-14 16:35:06 +09001635If a discovered file name beings with `servlet-` it will be clustered
1636into a 'Servlets' section of the generated index page.
1637
1638If a discovered file name beings with `rest-api-` it will be clustered
1639into a 'REST APIs' section of the generated index page.
1640
David Pursehouse6853b5a2013-07-10 11:38:03 +09001641All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001642
1643Some optional information from the manifest is extracted and
1644displayed as part of the index page, if present in the manifest:
1645
1646[width="40%",options="header"]
1647|===================================================
1648|Field | Source Attribute
1649|Name | Implementation-Title
1650|Vendor | Implementation-Vendor
1651|Version | Implementation-Version
1652|URL | Implementation-URL
1653|API Version | Gerrit-ApiVersion
1654|===================================================
1655
Edwin Kempinf5a77332012-07-18 11:17:53 +02001656[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001657Deployment
1658----------
1659
Edwin Kempinf7295742012-07-16 15:03:46 +02001660Compiled plugins and extensions can be deployed to a running Gerrit
1661server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001662
1663Plugins can also be copied directly into the server's
1664directory at `$site_path/plugins/$name.jar`. The name of
1665the JAR file, minus the `.jar` extension, will be used as the
1666plugin name. Unless disabled, servers periodically scan this
1667directory for updated plugins. The time can be adjusted by
1668link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001669
Edwin Kempinf7295742012-07-16 15:03:46 +02001670For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
1671command can be used.
1672
Brad Larsond5e87c32012-07-11 12:18:49 -05001673Disabled plugins can be re-enabled using the
1674link:cmd-plugin-enable.html[plugin enable] command.
1675
David Ostrovskyf86bae52013-09-01 09:10:39 +02001676SEE ALSO
1677--------
1678
1679* link:js-api.html[JavaScript API]
1680* link:dev-rest-api.html[REST API Developers' Notes]
1681
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001682GERRIT
1683------
1684Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07001685
1686SEARCHBOX
1687---------