blob: b21b78bf0f2f62bd99d15dd24b16dcb03ad7df8f [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 \
41 -DarchetypeVersion=2.5-SNAPSHOT \
42 -DgroupId=com.google.gerrit \
43 -DartifactId=testPlugin
44----
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
Edwin Kempinf7295742012-07-16 15:03:46 +0200135[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700136Reload Method
137~~~~~~~~~~~~~
138
139If a plugin holds an exclusive resource that must be released before
140loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200141acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700142to be `restart`. Otherwise the preferred method of `reload` will
143be used, as it enables the server to hot-patch an updated plugin
144with no down time.
145
146====
147 Gerrit-ReloadMode: restart
148====
149
150In either mode ('restart' or 'reload') any plugin or extension can
151be updated without restarting the Gerrit server. The difference is
152how Gerrit handles the upgrade:
153
154restart::
155 The old plugin is completely stopped. All registrations of SSH
156 commands and HTTP servlets are removed. All registrations of any
157 extension points are removed. All registered LifecycleListeners
158 have their `stop()` method invoked in reverse order. The new
159 plugin is started, and registrations are made from the new
160 plugin. There is a brief window where neither the old nor the
161 new plugin is connected to the server. This means SSH commands
162 and HTTP servlets will return not found errors, and the plugin
163 will not be notified of events that occurred during the restart.
164
165reload::
166 The new plugin is started. Its LifecycleListeners are permitted
167 to perform their `start()` methods. All SSH and HTTP registrations
168 are atomically swapped out from the old plugin to the new plugin,
169 ensuring the server never returns a not found error. All extension
170 point listeners are atomically swapped out from the old plugin to
171 the new plugin, ensuring no events are missed (however some events
172 may still route to the old plugin if the swap wasn't complete yet).
173 The old plugin is stopped.
174
Edwin Kempinf7295742012-07-16 15:03:46 +0200175To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
176command can be used.
177
Luca Milanesio737285d2012-09-25 14:26:43 +0100178[[init_step]]
179Init step
180~~~~~~~~~
181
182Plugins can contribute their own "init step" during the Gerrit init
183wizard. This is useful for guiding the Gerrit administrator through
184the settings needed by the plugin to work propertly.
185
186For instance plugins to integrate Jira issues to Gerrit changes may
187contribute their own "init step" to allow configuring the Jira URL,
188credentials and possibly verify connectivity to validate them.
189
190====
191 Gerrit-InitStep: tld.example.project.MyInitStep
192====
193
194MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900195and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100196and accessing / changing configuration settings using Section.Factory.
197
198In addition to the standard Gerrit init injections, plugins receive
199the @PluginName String injection containing their own plugin name.
200
201Bear in mind that the Plugin's InitStep class will be loaded but
202the standard Gerrit runtime environment is not available and the plugin's
203own Guice modules were not initialized.
204This means the InitStep for a plugin is not executed in the same way that
205the plugin executes within the server, and may mean a plugin author cannot
206trivially reuse runtime code during init.
207
208For instance a plugin that wants to verify connectivity may need to statically
209call the constructor of their connection class, passing in values obtained
210from the Section.Factory rather than from an injected Config object.
211
212Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
213the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
214and before the DB Schema initialization or upgrade.
215Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
216objects injected at startup.
217
David Pursehouse68153d72013-09-04 10:09:17 +0900218[source,java]
219----
220public class MyInitStep implements InitStep {
221 private final ConsoleUI ui;
222 private final Section.Factory sections;
223 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100224
David Pursehouse68153d72013-09-04 10:09:17 +0900225 @Inject
226 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
227 this.ui = ui;
228 this.sections = sections;
229 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100230 }
David Pursehouse68153d72013-09-04 10:09:17 +0900231
232 @Override
233 public void run() throws Exception {
234 ui.header("\nMy plugin");
235
236 Section mySection = getSection("myplugin", null);
237 mySection.string("Link name", "linkname", "MyLink");
238 }
239}
240----
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
Edwin Kempinf5a77332012-07-18 11:17:53 +0200242[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700243Classpath
244---------
245
246Each plugin is loaded into its own ClassLoader, isolating plugins
247from each other. A plugin or extension inherits the Java runtime
248and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
249from the hosting server.
250
251Plugins are loaded from a single JAR file. If a plugin needs
252additional libraries, it must include those dependencies within
253its own JAR. Plugins built using Maven may be able to use the
254link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
255to package additional dependencies. Relocating (or renaming) classes
256should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700257
Edwin Kempin98202662013-09-18 16:03:03 +0200258[[events]]
259Listening to Events
260-------------------
261
262Certain operations in Gerrit trigger events. Plugins may receive
263notifications of these events by implementing the corresponding
264listeners.
265
266* `com.google.gerrit.extensions.events.LifecycleListener`:
267+
268Gerrit server startup and shutdown
269
270* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
271+
272Project creation
273
274* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
275+
276Project deletion
277
Edwin Kempinf5a77332012-07-18 11:17:53 +0200278[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700279SSH Commands
280------------
281
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700282Plugins may provide commands that can be accessed through the SSH
283interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700284
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700285Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700286
David Pursehouse68153d72013-09-04 10:09:17 +0900287[source,java]
288----
289import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700290
David Pursehouse68153d72013-09-04 10:09:17 +0900291class PrintHello extends SshCommand {
292 protected abstract void run() {
293 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700294 }
David Pursehouse68153d72013-09-04 10:09:17 +0900295}
296----
Nasser Grainawie033b262012-05-09 17:54:21 -0700297
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700298If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200299use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700300
David Pursehouse68153d72013-09-04 10:09:17 +0900301[source,java]
302----
303import com.google.gerrit.extensions.annotations.Export;
304import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700305
David Pursehouse68153d72013-09-04 10:09:17 +0900306@Export("print")
307class PrintHello extends SshCommand {
308 protected abstract void run() {
309 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700310 }
David Pursehouse68153d72013-09-04 10:09:17 +0900311}
312----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700313
314If explicit registration is being used, a Guice module must be
315supplied to register the SSH command and declared in the manifest
316with the `Gerrit-SshModule` attribute:
317
David Pursehouse68153d72013-09-04 10:09:17 +0900318[source,java]
319----
320import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700321
David Pursehouse68153d72013-09-04 10:09:17 +0900322class MyCommands extends PluginCommandModule {
323 protected void configureCommands() {
324 command("print").to(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700325 }
David Pursehouse68153d72013-09-04 10:09:17 +0900326}
327----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700328
329For a plugin installed as name `helloworld`, the command implemented
330by PrintHello class will be available to users as:
331
332----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600333$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700334----
335
David Ostrovsky7066cc02013-06-15 14:46:23 +0200336[[capabilities]]
337Plugin Owned Capabilities
338-------------------------
339
340Plugins may provide their own capabilities and restrict usage of SSH
341commands to the users who are granted those capabilities.
342
343Plugins define the capabilities by overriding the `CapabilityDefinition`
344abstract class:
345
David Pursehouse68153d72013-09-04 10:09:17 +0900346[source,java]
347----
348public class PrintHelloCapability extends CapabilityDefinition {
349 @Override
350 public String getDescription() {
351 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200352 }
David Pursehouse68153d72013-09-04 10:09:17 +0900353}
354----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200355
David Ostrovskyf86bae52013-09-01 09:10:39 +0200356If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200357use auto-registration by providing an `@Export` annotation:
358
David Pursehouse68153d72013-09-04 10:09:17 +0900359[source,java]
360----
361@Export("printHello")
362public class PrintHelloCapability extends CapabilityDefinition {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200363 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900364}
365----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200366
367Otherwise the capability must be bound in a plugin module:
368
David Pursehouse68153d72013-09-04 10:09:17 +0900369[source,java]
370----
371public class HelloWorldModule extends AbstractModule {
372 @Override
373 protected void configure() {
374 bind(CapabilityDefinition.class)
375 .annotatedWith(Exports.named("printHello"))
376 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200377 }
David Pursehouse68153d72013-09-04 10:09:17 +0900378}
379----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200380
381With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200382usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200383this capability in the usual way, using the `RequiresCapability` annotation:
384
David Pursehouse68153d72013-09-04 10:09:17 +0900385[source,java]
386----
387@RequiresCapability("printHello")
388@CommandMetaData(name="print", description="Print greeting in different languages")
389public final class PrintHelloWorldCommand extends SshCommand {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200390 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900391}
392----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200393
David Ostrovskyf86bae52013-09-01 09:10:39 +0200394Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200395
David Pursehouse68153d72013-09-04 10:09:17 +0900396[source,java]
397----
398@RequiresCapability("printHello")
399public class SayHelloAction extends UiAction<RevisionResource>
400 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200401 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900402}
403----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200404
405Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900406capabilities and core capabilities. Per default the scope of the
407`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
408
David Ostrovsky7066cc02013-06-15 14:46:23 +0200409* when `@RequiresCapability` is used within a plugin the scope of the
410capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900411
David Ostrovsky7066cc02013-06-15 14:46:23 +0200412* If `@RequiresCapability` is used within the core Gerrit Code Review server
413(and thus is outside of a plugin) the scope is the core server and will use
414the `GlobalCapability` known to Gerrit Code Review server.
415
416If a plugin needs to use a core capability name (e.g. "administrateServer")
417this can be specified by setting `scope = CapabilityScope.CORE`:
418
David Pursehouse68153d72013-09-04 10:09:17 +0900419[source,java]
420----
421@RequiresCapability(value = "administrateServer", scope =
422 CapabilityScope.CORE)
David Ostrovsky7066cc02013-06-15 14:46:23 +0200423 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900424----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200425
David Ostrovskyf86bae52013-09-01 09:10:39 +0200426[[ui_extension]]
427UI Extension
428------------
429
430Plugins can contribute their own UI commands on core Gerrit pages.
431This is useful for workflow customization or exposing plugin functionality
432through the UI in addition to SSH commands and the REST API.
433
434For instance a plugin to integrate Jira with Gerrit changes may contribute its
435own "File bug" button to allow filing a bug from the change page or plugins to
436integrate continuous integration systems may contribute a "Schedule" button to
437allow a CI build to be scheduled manually from the patch set panel.
438
439Two different places on core Gerrit pages are currently supported:
440
441* Change screen
442* Project info screen
443
444Plugins contribute UI actions by implementing the `UiAction` interface:
445
David Pursehouse68153d72013-09-04 10:09:17 +0900446[source,java]
447----
448@RequiresCapability("printHello")
449class HelloWorldAction implements UiAction<RevisionResource>,
450 RestModifyView<RevisionResource, HelloWorldAction.Input> {
451 static class Input {
452 boolean french;
453 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200454 }
David Pursehouse68153d72013-09-04 10:09:17 +0900455
456 private Provider<CurrentUser> user;
457
458 @Inject
459 HelloWorldAction(Provider<CurrentUser> user) {
460 this.user = user;
461 }
462
463 @Override
464 public String apply(RevisionResource rev, Input input) {
465 final String greeting = input.french
466 ? "Bonjour"
467 : "Hello";
468 return String.format("%s %s from change %s, patch set %d!",
469 greeting,
470 Strings.isNullOrEmpty(input.message)
471 ? Objects.firstNonNull(user.get().getUserName(), "world")
472 : input.message,
473 rev.getChange().getId().toString(),
474 rev.getPatchSet().getPatchSetId());
475 }
476
477 @Override
478 public Description getDescription(
479 RevisionResource resource) {
480 return new Description()
481 .setLabel("Say hello")
482 .setTitle("Say hello in different languages");
483 }
484}
485----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200486
487`UiAction` must be bound in a plugin module:
488
David Pursehouse68153d72013-09-04 10:09:17 +0900489[source,java]
490----
491public class Module extends AbstractModule {
492 @Override
493 protected void configure() {
494 install(new RestApiModule() {
495 @Override
496 protected void configure() {
497 post(REVISION_KIND, "say-hello")
498 .to(HelloWorldAction.class);
499 }
500 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200501 }
David Pursehouse68153d72013-09-04 10:09:17 +0900502}
503----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200504
505The module above must be declared in pom.xml for Maven driven plugins:
506
David Pursehouse68153d72013-09-04 10:09:17 +0900507[source,xml]
508----
509<manifestEntries>
510 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
511</manifestEntries>
512----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200513
514or in the BUCK configuration file for Buck driven plugins:
515
David Pursehouse68153d72013-09-04 10:09:17 +0900516[source,python]
517----
518manifest_entries = [
519 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
520]
521----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200522
523In some use cases more user input must be gathered, for that `UiAction` can be
524combined with the JavaScript API. This would display a small popup near the
525activation button to gather additional input from the user. The JS file is
526typically put in the `static` folder within the plugin's directory:
527
David Pursehouse68153d72013-09-04 10:09:17 +0900528[source,javascript]
529----
530Gerrit.install(function(self) {
531 function onSayHello(c) {
532 var f = c.textfield();
533 var t = c.checkbox();
534 var b = c.button('Say hello', {onclick: function(){
535 c.call(
536 {message: f.value, french: t.checked},
537 function(r) {
538 c.hide();
539 window.alert(r);
540 c.refresh();
541 });
542 }});
543 c.popup(c.div(
544 c.prependLabel('Greeting message', f),
545 c.br(),
546 c.label(t, 'french'),
547 c.br(),
548 b));
549 f.focus();
550 }
551 self.onAction('revision', 'say-hello', onSayHello);
552});
553----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200554
555The JS module must be exposed as a `WebUiPlugin` and bound as
556an HTTP Module:
557
David Pursehouse68153d72013-09-04 10:09:17 +0900558[source,java]
559----
560public class HttpModule extends HttpPluginModule {
561 @Override
562 protected void configureServlets() {
563 DynamicSet.bind(binder(), WebUiPlugin.class)
564 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200565 }
David Pursehouse68153d72013-09-04 10:09:17 +0900566}
567----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200568
569The HTTP module above must be declared in pom.xml for Maven driven plugins:
570
David Pursehouse68153d72013-09-04 10:09:17 +0900571[source,xml]
572----
573<manifestEntries>
574 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
575</manifestEntries>
576----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200577
578or in the BUCK configuration file for Buck driven plugins
579
David Pursehouse68153d72013-09-04 10:09:17 +0900580[source,python]
581----
582manifest_entries = [
583 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
584]
585----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200586
587If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
588capability check is done during the `UiAction` gathering, so the plugin author
589doesn't have to set `UiAction.Description.setVisible()` explicitly in this
590case.
591
592The following prerequisities must be met, to satisfy the capability check:
593
594* user is authenticated
595* user is a member of the Administrators group, or
596* user is a member of a group which has the required capability
597
598The `apply` method is called when the button is clicked. If `UiAction` is
599combined with JavaScript API (its own JavaScript function is provided),
600then a popup dialog is normally opened to gather additional user input.
601A new button is placed on the popup dialog to actually send the request.
602
603Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
604can be accessed from any REST client, i. e.:
605
606====
607 curl -X POST -H "Content-Type: application/json" \
608 -d '{message: "François", french: true}' \
609 --digest --user joe:secret \
610 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
611 "Bonjour François from change 1, patch set 1!"
612====
613
Edwin Kempinf5a77332012-07-18 11:17:53 +0200614[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700615HTTP Servlets
616-------------
617
618Plugins or extensions may register additional HTTP servlets, and
619wrap them with HTTP filters.
620
621Servlets may use auto-registration to declare the URL they handle:
622
David Pursehouse68153d72013-09-04 10:09:17 +0900623[source,java]
624----
625import com.google.gerrit.extensions.annotations.Export;
626import com.google.inject.Singleton;
627import javax.servlet.http.HttpServlet;
628import javax.servlet.http.HttpServletRequest;
629import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700630
David Pursehouse68153d72013-09-04 10:09:17 +0900631@Export("/print")
632@Singleton
633class HelloServlet extends HttpServlet {
634 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
635 res.setContentType("text/plain");
636 res.setCharacterEncoding("UTF-8");
637 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700638 }
David Pursehouse68153d72013-09-04 10:09:17 +0900639}
640----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700641
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200642The auto registration only works for standard servlet mappings like
643`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
644to register the HTTP servlets and declare it explicitly in the manifest
645with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700646
David Pursehouse68153d72013-09-04 10:09:17 +0900647[source,java]
648----
649import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700650
David Pursehouse68153d72013-09-04 10:09:17 +0900651class MyWebUrls extends ServletModule {
652 protected void configureServlets() {
653 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700654 }
David Pursehouse68153d72013-09-04 10:09:17 +0900655}
656----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700657
658For a plugin installed as name `helloworld`, the servlet implemented
659by HelloServlet class will be available to users as:
660
661----
662$ curl http://review.example.com/plugins/helloworld/print
663----
Nasser Grainawie033b262012-05-09 17:54:21 -0700664
Edwin Kempinf5a77332012-07-18 11:17:53 +0200665[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200666Data Directory
667--------------
668
669Plugins can request a data directory with a `@PluginData` File
670dependency. A data directory will be created automatically by the
671server in `$site_path/data/$plugin_name` and passed to the plugin.
672
673Plugins can use this to store any data they want.
674
David Pursehouse68153d72013-09-04 10:09:17 +0900675[source,java]
676----
677@Inject
678MyType(@PluginData java.io.File myDir) {
679 new FileInputStream(new File(myDir, "my.config"));
680}
681----
Edwin Kempin41f63912012-07-17 12:33:55 +0200682
Edwin Kempinf5a77332012-07-18 11:17:53 +0200683[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700684Documentation
685-------------
686
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700687If a plugin does not register a filter or servlet to handle URLs
688`/Documentation/*` or `/static/*`, the core Gerrit server will
689automatically export these resources over HTTP from the plugin JAR.
690
David Pursehouse6853b5a2013-07-10 11:38:03 +0900691Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -0400692available as `/plugins/helloworld/static/resource`. This prefix is
693configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700694
David Pursehouse6853b5a2013-07-10 11:38:03 +0900695Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -0400696will be available as `/plugins/helloworld/Documentation/resource`. This
697prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
698attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700699
700Documentation may be written in
701link:http://daringfireball.net/projects/markdown/[Markdown] style
702if the file name ends with `.md`. Gerrit will automatically convert
703Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700704
Edwin Kempinf5a77332012-07-18 11:17:53 +0200705[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200706Within the Markdown documentation files macros can be used that allow
707to write documentation with reasonably accurate examples that adjust
708automatically based on the installation.
709
710The following macros are supported:
711
712[width="40%",options="header"]
713|===================================================
714|Macro | Replacement
715|@PLUGIN@ | name of the plugin
716|@URL@ | Gerrit Web URL
717|@SSH_HOST@ | SSH Host
718|@SSH_PORT@ | SSH Port
719|===================================================
720
721The macros will be replaced when the documentation files are rendered
722from Markdown to HTML.
723
724Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
725even if there is an expansion for `KEEP` in the future.
726
Edwin Kempinf5a77332012-07-18 11:17:53 +0200727[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700728Automatic Index
729~~~~~~~~~~~~~~~
730
731If a plugin does not handle its `/` URL itself, Gerrit will
732redirect clients to the plugin's `/Documentation/index.html`.
733Requests for `/Documentation/` (bare directory) will also redirect
734to `/Documentation/index.html`.
735
736If neither resource `Documentation/index.html` or
737`Documentation/index.md` exists in the plugin JAR, Gerrit will
738automatically generate an index page for the plugin's documentation
739tree by scanning every `*.md` and `*.html` file in the Documentation/
740directory.
741
742For any discovered Markdown (`*.md`) file, Gerrit will parse the
743header of the file and extract the first level one title. This
744title text will be used as display text for a link to the HTML
745version of the page.
746
747For any discovered HTML (`*.html`) file, Gerrit will use the name
748of the file, minus the `*.html` extension, as the link text. Any
749hyphens in the file name will be replaced with spaces.
750
David Pursehouse6853b5a2013-07-10 11:38:03 +0900751If a discovered file is named `about.md` or `about.html`, its
752content will be inserted in an 'About' section at the top of the
753auto-generated index page. If both `about.md` and `about.html`
754exist, only the first discovered file will be used.
755
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700756If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +0900757into a 'Commands' section of the generated index page.
758
David Pursehousefe529152013-08-14 16:35:06 +0900759If a discovered file name beings with `servlet-` it will be clustered
760into a 'Servlets' section of the generated index page.
761
762If a discovered file name beings with `rest-api-` it will be clustered
763into a 'REST APIs' section of the generated index page.
764
David Pursehouse6853b5a2013-07-10 11:38:03 +0900765All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700766
767Some optional information from the manifest is extracted and
768displayed as part of the index page, if present in the manifest:
769
770[width="40%",options="header"]
771|===================================================
772|Field | Source Attribute
773|Name | Implementation-Title
774|Vendor | Implementation-Vendor
775|Version | Implementation-Version
776|URL | Implementation-URL
777|API Version | Gerrit-ApiVersion
778|===================================================
779
Edwin Kempinf5a77332012-07-18 11:17:53 +0200780[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700781Deployment
782----------
783
Edwin Kempinf7295742012-07-16 15:03:46 +0200784Compiled plugins and extensions can be deployed to a running Gerrit
785server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700786
787Plugins can also be copied directly into the server's
788directory at `$site_path/plugins/$name.jar`. The name of
789the JAR file, minus the `.jar` extension, will be used as the
790plugin name. Unless disabled, servers periodically scan this
791directory for updated plugins. The time can be adjusted by
792link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700793
Edwin Kempinf7295742012-07-16 15:03:46 +0200794For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
795command can be used.
796
Brad Larsond5e87c32012-07-11 12:18:49 -0500797Disabled plugins can be re-enabled using the
798link:cmd-plugin-enable.html[plugin enable] command.
799
David Ostrovskyf86bae52013-09-01 09:10:39 +0200800SEE ALSO
801--------
802
803* link:js-api.html[JavaScript API]
804* link:dev-rest-api.html[REST API Developers' Notes]
805
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700806GERRIT
807------
808Part of link:index.html[Gerrit Code Review]