blob: caa6dbe857de654653784ea60723990bc0da14a0 [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 Kempinf878c4b2012-07-18 09:34:25 +020042 -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
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
283Bear in mind that the Plugin's InitStep class will be loaded but
284the standard Gerrit runtime environment is not available and the plugin's
285own Guice modules were not initialized.
286This means the InitStep for a plugin is not executed in the same way that
287the plugin executes within the server, and may mean a plugin author cannot
288trivially reuse runtime code during init.
289
290For instance a plugin that wants to verify connectivity may need to statically
291call the constructor of their connection class, passing in values obtained
292from the Section.Factory rather than from an injected Config object.
293
David Pursehoused128c892013-10-22 21:52:21 +0900294Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
295the extraction of the plugins embedded in the distribution .war file into
296`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
297
298A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
299runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100300
David Pursehouse68153d72013-09-04 10:09:17 +0900301[source,java]
302----
303public class MyInitStep implements InitStep {
304 private final ConsoleUI ui;
305 private final Section.Factory sections;
306 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100307
David Pursehouse68153d72013-09-04 10:09:17 +0900308 @Inject
309 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
310 this.ui = ui;
311 this.sections = sections;
312 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100313 }
David Pursehouse68153d72013-09-04 10:09:17 +0900314
315 @Override
316 public void run() throws Exception {
317 ui.header("\nMy plugin");
318
319 Section mySection = getSection("myplugin", null);
320 mySection.string("Link name", "linkname", "MyLink");
321 }
322}
323----
Luca Milanesio737285d2012-09-25 14:26:43 +0100324
Edwin Kempinf5a77332012-07-18 11:17:53 +0200325[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700326Classpath
327---------
328
329Each plugin is loaded into its own ClassLoader, isolating plugins
330from each other. A plugin or extension inherits the Java runtime
331and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
332from the hosting server.
333
334Plugins are loaded from a single JAR file. If a plugin needs
335additional libraries, it must include those dependencies within
336its own JAR. Plugins built using Maven may be able to use the
337link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
338to package additional dependencies. Relocating (or renaming) classes
339should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700340
Edwin Kempin98202662013-09-18 16:03:03 +0200341[[events]]
342Listening to Events
343-------------------
344
345Certain operations in Gerrit trigger events. Plugins may receive
346notifications of these events by implementing the corresponding
347listeners.
348
Edwin Kempin64059f52013-10-31 13:49:25 +0100349* `com.google.gerrit.common.ChangeListener`:
350+
351Allows to listen to change events. These are the same
352link:cmd-stream-events.html#events[events] that are also streamed by
353the link:cmd-stream-events.html[gerrit stream-events] command.
354
Edwin Kempin98202662013-09-18 16:03:03 +0200355* `com.google.gerrit.extensions.events.LifecycleListener`:
356+
357Gerrit server startup and shutdown
358
359* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
360+
361Project creation
362
363* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
364+
365Project deletion
366
Yang Zhenhui2659d422013-07-30 16:59:58 +0800367[[stream-events]]
368Sending Events to the Events Stream
369-----------------------------------
370
371Plugins may send events to the events stream where consumers of
372Gerrit's `stream-events` ssh command will receive them.
373
374To send an event, the plugin must invoke one of the `postEvent`
375methods in the `ChangeHookRunner` class, passing an instance of
376its own custom event class derived from `ChangeEvent`.
377
Edwin Kempinf5a77332012-07-18 11:17:53 +0200378[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700379SSH Commands
380------------
381
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700382Plugins may provide commands that can be accessed through the SSH
383interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700384
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700385Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700386
David Pursehouse68153d72013-09-04 10:09:17 +0900387[source,java]
388----
389import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100390import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700391
David Ostrovskyb7d97752013-11-09 05:23:26 +0100392@CommandMetaData(name="print", descr="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900393class PrintHello extends SshCommand {
394 protected abstract void run() {
395 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700396 }
David Pursehouse68153d72013-09-04 10:09:17 +0900397}
398----
Nasser Grainawie033b262012-05-09 17:54:21 -0700399
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700400If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200401use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700402
David Pursehouse68153d72013-09-04 10:09:17 +0900403[source,java]
404----
405import com.google.gerrit.extensions.annotations.Export;
406import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700407
David Pursehouse68153d72013-09-04 10:09:17 +0900408@Export("print")
409class PrintHello extends SshCommand {
410 protected abstract void run() {
411 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700412 }
David Pursehouse68153d72013-09-04 10:09:17 +0900413}
414----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700415
416If explicit registration is being used, a Guice module must be
417supplied to register the SSH command and declared in the manifest
418with the `Gerrit-SshModule` attribute:
419
David Pursehouse68153d72013-09-04 10:09:17 +0900420[source,java]
421----
422import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700423
David Pursehouse68153d72013-09-04 10:09:17 +0900424class MyCommands extends PluginCommandModule {
425 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100426 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700427 }
David Pursehouse68153d72013-09-04 10:09:17 +0900428}
429----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700430
431For a plugin installed as name `helloworld`, the command implemented
432by PrintHello class will be available to users as:
433
434----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600435$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700436----
437
David Ostrovskye3172b32013-10-13 14:19:13 +0200438Multiple SSH commands can be bound to the same implementation class. For
439example a Gerrit Shell plugin can bind different shell commands to the same
440implementation class:
441
442[source,java]
443----
444public class SshShellModule extends PluginCommandModule {
445 @Override
446 protected void configureCommands() {
447 command("ls").to(ShellCommand.class);
448 command("ps").to(ShellCommand.class);
449 [...]
450 }
451}
452----
453
454With the possible implementation:
455
456[source,java]
457----
458public class ShellCommand extends SshCommand {
459 @Override
460 protected void run() throws UnloggedFailure {
461 String cmd = getName().substring(getPluginName().length() + 1);
462 ProcessBuilder proc = new ProcessBuilder(cmd);
463 Process cmd = proc.start();
464 [...]
465 }
466}
467----
468
469And the call:
470
471----
472$ ssh -p 29418 review.example.com shell ls
473$ ssh -p 29418 review.example.com shell ps
474----
475
David Ostrovskyb7d97752013-11-09 05:23:26 +0100476Single command plugins are also supported. In this scenario plugin binds
477SSH command to its own name. `SshModule` must inherit from
478`SingleCommandPluginModule` class:
479
480[source,java]
481----
482public class SshModule extends SingleCommandPluginModule {
483 @Override
484 protected void configure(LinkedBindingBuilder<Command> b) {
485 b.to(ShellCommand.class);
486 }
487}
488----
489
490If the plugin above is deployed under sh.jar file in `$site/plugins`
491directory, generic commands can be called without specifing the
492actual SSH command. Note in the example below, that the called commands
493`ls` and `ps` was not explicitly bound:
494
495----
496$ ssh -p 29418 review.example.com sh ls
497$ ssh -p 29418 review.example.com sh ps
498----
499
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200500[[configuration]]
501Configuration
502-------------
503
504In Gerrit, global configuration is stored in the `gerrit.config` file.
505If a plugin needs global configuration, this configuration should be
506stored in a `plugin` subsection in the `gerrit.config` file.
507
Edwin Kempinc9b68602013-10-30 09:32:43 +0100508This approach of storing the plugin configuration is only suitable for
509plugins that have a simple configuration that only consists of
510key-value pairs. With this approach it is not possible to have
511subsections in the plugin configuration. Plugins that require a complex
512configuration need to store their configuration in their own
513configuration file where they can make use of subsections. On the other
514hand storing the plugin configuration in a 'plugin' subsection in the
515`gerrit.config` file has the advantage that administrators have all
516configuration parameters in one file, instead of having one
517configuration file per plugin.
518
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200519To avoid conflicts with other plugins, it is recommended that plugins
520only use the `plugin` subsection with their own name. For example the
521`helloworld` plugin should store its configuration in the
522`plugin.helloworld` subsection:
523
524----
525[plugin "helloworld"]
526 language = Latin
527----
528
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200529Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200530plugin can easily access its configuration and there is no need for a
531plugin to parse the `gerrit.config` file on its own:
532
533[source,java]
534----
David Pursehouse529ec252013-09-27 13:45:14 +0900535@Inject
536private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200537
David Pursehoused128c892013-10-22 21:52:21 +0900538[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200539
Edwin Kempin122622d2013-10-29 16:45:44 +0100540String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900541 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200542----
543
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200544[[project-specific-configuration]]
545Project Specific Configuration
546------------------------------
547
548In Gerrit, project specific configuration is stored in the project's
549`project.config` file on the `refs/meta/config` branch. If a plugin
550needs configuration on project level (e.g. to enable its functionality
551only for certain projects), this configuration should be stored in a
552`plugin` subsection in the project's `project.config` file.
553
Edwin Kempinc9b68602013-10-30 09:32:43 +0100554This approach of storing the plugin configuration is only suitable for
555plugins that have a simple configuration that only consists of
556key-value pairs. With this approach it is not possible to have
557subsections in the plugin configuration. Plugins that require a complex
558configuration need to store their configuration in their own
559configuration file where they can make use of subsections. On the other
560hand storing the plugin configuration in a 'plugin' subsection in the
561`project.config` file has the advantage that project owners have all
562configuration parameters in one file, instead of having one
563configuration file per plugin.
564
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200565To avoid conflicts with other plugins, it is recommended that plugins
566only use the `plugin` subsection with their own name. For example the
567`helloworld` plugin should store its configuration in the
568`plugin.helloworld` subsection:
569
570----
571 [plugin "helloworld"]
572 enabled = true
573----
574
575Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
576plugin can easily access its project specific configuration and there
577is no need for a plugin to parse the `project.config` file on its own:
578
579[source,java]
580----
David Pursehouse529ec252013-09-27 13:45:14 +0900581@Inject
582private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200583
David Pursehoused128c892013-10-22 21:52:21 +0900584[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200585
Edwin Kempin122622d2013-10-29 16:45:44 +0100586boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900587 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200588----
589
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200590It is also possible to get missing configuration parameters inherited
591from the parent projects:
592
593[source,java]
594----
David Pursehouse529ec252013-09-27 13:45:14 +0900595@Inject
596private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200597
David Pursehoused128c892013-10-22 21:52:21 +0900598[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200599
Edwin Kempin122622d2013-10-29 16:45:44 +0100600boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900601 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200602----
603
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200604Project owners can edit the project configuration by fetching the
605`refs/meta/config` branch, editing the `project.config` file and
606pushing the commit back.
607
David Ostrovsky7066cc02013-06-15 14:46:23 +0200608[[capabilities]]
609Plugin Owned Capabilities
610-------------------------
611
612Plugins may provide their own capabilities and restrict usage of SSH
613commands to the users who are granted those capabilities.
614
615Plugins define the capabilities by overriding the `CapabilityDefinition`
616abstract class:
617
David Pursehouse68153d72013-09-04 10:09:17 +0900618[source,java]
619----
620public class PrintHelloCapability extends CapabilityDefinition {
621 @Override
622 public String getDescription() {
623 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200624 }
David Pursehouse68153d72013-09-04 10:09:17 +0900625}
626----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200627
David Ostrovskyf86bae52013-09-01 09:10:39 +0200628If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200629use auto-registration by providing an `@Export` annotation:
630
David Pursehouse68153d72013-09-04 10:09:17 +0900631[source,java]
632----
633@Export("printHello")
634public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +0900635 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900636}
637----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200638
639Otherwise the capability must be bound in a plugin module:
640
David Pursehouse68153d72013-09-04 10:09:17 +0900641[source,java]
642----
643public class HelloWorldModule extends AbstractModule {
644 @Override
645 protected void configure() {
646 bind(CapabilityDefinition.class)
647 .annotatedWith(Exports.named("printHello"))
648 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200649 }
David Pursehouse68153d72013-09-04 10:09:17 +0900650}
651----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200652
653With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200654usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200655this capability in the usual way, using the `RequiresCapability` annotation:
656
David Pursehouse68153d72013-09-04 10:09:17 +0900657[source,java]
658----
659@RequiresCapability("printHello")
660@CommandMetaData(name="print", description="Print greeting in different languages")
661public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +0900662 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900663}
664----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200665
David Ostrovskyf86bae52013-09-01 09:10:39 +0200666Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200667
David Pursehouse68153d72013-09-04 10:09:17 +0900668[source,java]
669----
670@RequiresCapability("printHello")
671public class SayHelloAction extends UiAction<RevisionResource>
672 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +0900673 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900674}
675----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200676
677Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900678capabilities and core capabilities. Per default the scope of the
679`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
680
David Ostrovsky7066cc02013-06-15 14:46:23 +0200681* when `@RequiresCapability` is used within a plugin the scope of the
682capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900683
David Ostrovsky7066cc02013-06-15 14:46:23 +0200684* If `@RequiresCapability` is used within the core Gerrit Code Review server
685(and thus is outside of a plugin) the scope is the core server and will use
686the `GlobalCapability` known to Gerrit Code Review server.
687
688If a plugin needs to use a core capability name (e.g. "administrateServer")
689this can be specified by setting `scope = CapabilityScope.CORE`:
690
David Pursehouse68153d72013-09-04 10:09:17 +0900691[source,java]
692----
693@RequiresCapability(value = "administrateServer", scope =
694 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +0900695 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900696----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200697
David Ostrovskyf86bae52013-09-01 09:10:39 +0200698[[ui_extension]]
699UI Extension
700------------
701
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100702Plugins can contribute UI actions on core Gerrit pages. This is useful
703for workflow customization or exposing plugin functionality through the
704UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200705
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100706For instance a plugin to integrate Jira with Gerrit changes may
707contribute a "File bug" button to allow filing a bug from the change
708page or plugins to integrate continuous integration systems may
709contribute a "Schedule" button to allow a CI build to be scheduled
710manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200711
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100712Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200713
714* Change screen
715* Project info screen
716
717Plugins contribute UI actions by implementing the `UiAction` interface:
718
David Pursehouse68153d72013-09-04 10:09:17 +0900719[source,java]
720----
721@RequiresCapability("printHello")
722class HelloWorldAction implements UiAction<RevisionResource>,
723 RestModifyView<RevisionResource, HelloWorldAction.Input> {
724 static class Input {
725 boolean french;
726 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200727 }
David Pursehouse68153d72013-09-04 10:09:17 +0900728
729 private Provider<CurrentUser> user;
730
731 @Inject
732 HelloWorldAction(Provider<CurrentUser> user) {
733 this.user = user;
734 }
735
736 @Override
737 public String apply(RevisionResource rev, Input input) {
738 final String greeting = input.french
739 ? "Bonjour"
740 : "Hello";
741 return String.format("%s %s from change %s, patch set %d!",
742 greeting,
743 Strings.isNullOrEmpty(input.message)
744 ? Objects.firstNonNull(user.get().getUserName(), "world")
745 : input.message,
746 rev.getChange().getId().toString(),
747 rev.getPatchSet().getPatchSetId());
748 }
749
750 @Override
751 public Description getDescription(
752 RevisionResource resource) {
753 return new Description()
754 .setLabel("Say hello")
755 .setTitle("Say hello in different languages");
756 }
757}
758----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200759
David Ostrovsky450eefe2013-10-21 21:18:11 +0200760Sometimes plugins may want to be able to change the state of a patch set or
761change in the `UiAction.apply()` method and reflect these changes on the core
762UI. For example a buildbot plugin which exposes a 'Schedule' button on the
763patch set panel may want to disable that button after the build was scheduled
764and update the tooltip of that button. But because of Gerrit's caching
765strategy the following must be taken into consideration.
766
767The browser is allowed to cache the `UiAction` information until something on
768the change is modified. More accurately the change row needs to be modified in
769the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
770the +refs/meta/config+ of the project or any parents needs to change to a new
771SHA-1. The ETag SHA-1 computation code can be found in the
772`ChangeResource.getETag()` method.
773
David Pursehoused128c892013-10-22 21:52:21 +0900774The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +0200775
776[source,java]
777----
778@Override
779public Object apply(RevisionResource rcrs, Input in) {
780 // schedule a build
781 [...]
782 // update change
783 ReviewDb db = dbProvider.get();
784 db.changes().beginTransaction(change.getId());
785 try {
786 change = db.changes().atomicUpdate(
787 change.getId(),
788 new AtomicUpdate<Change>() {
789 @Override
790 public Change update(Change change) {
791 ChangeUtil.updated(change);
792 return change;
793 }
794 });
795 db.commit();
796 } finally {
797 db.rollback();
798 }
David Pursehoused128c892013-10-22 21:52:21 +0900799 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +0200800}
801----
802
David Ostrovskyf86bae52013-09-01 09:10:39 +0200803`UiAction` must be bound in a plugin module:
804
David Pursehouse68153d72013-09-04 10:09:17 +0900805[source,java]
806----
807public class Module extends AbstractModule {
808 @Override
809 protected void configure() {
810 install(new RestApiModule() {
811 @Override
812 protected void configure() {
813 post(REVISION_KIND, "say-hello")
814 .to(HelloWorldAction.class);
815 }
816 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200817 }
David Pursehouse68153d72013-09-04 10:09:17 +0900818}
819----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200820
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100821The module above must be declared in the `pom.xml` for Maven driven
822plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200823
David Pursehouse68153d72013-09-04 10:09:17 +0900824[source,xml]
825----
826<manifestEntries>
827 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
828</manifestEntries>
829----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200830
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100831or in the `BUCK` configuration file for Buck driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200832
David Pursehouse68153d72013-09-04 10:09:17 +0900833[source,python]
834----
835manifest_entries = [
836 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
837]
838----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200839
840In some use cases more user input must be gathered, for that `UiAction` can be
841combined with the JavaScript API. This would display a small popup near the
842activation button to gather additional input from the user. The JS file is
843typically put in the `static` folder within the plugin's directory:
844
David Pursehouse68153d72013-09-04 10:09:17 +0900845[source,javascript]
846----
847Gerrit.install(function(self) {
848 function onSayHello(c) {
849 var f = c.textfield();
850 var t = c.checkbox();
851 var b = c.button('Say hello', {onclick: function(){
852 c.call(
853 {message: f.value, french: t.checked},
854 function(r) {
855 c.hide();
856 window.alert(r);
857 c.refresh();
858 });
859 }});
860 c.popup(c.div(
861 c.prependLabel('Greeting message', f),
862 c.br(),
863 c.label(t, 'french'),
864 c.br(),
865 b));
866 f.focus();
867 }
868 self.onAction('revision', 'say-hello', onSayHello);
869});
870----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200871
872The JS module must be exposed as a `WebUiPlugin` and bound as
873an HTTP Module:
874
David Pursehouse68153d72013-09-04 10:09:17 +0900875[source,java]
876----
877public class HttpModule extends HttpPluginModule {
878 @Override
879 protected void configureServlets() {
880 DynamicSet.bind(binder(), WebUiPlugin.class)
881 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200882 }
David Pursehouse68153d72013-09-04 10:09:17 +0900883}
884----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200885
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100886The HTTP module above must be declared in the `pom.xml` for Maven
887driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200888
David Pursehouse68153d72013-09-04 10:09:17 +0900889[source,xml]
890----
891<manifestEntries>
892 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
893</manifestEntries>
894----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200895
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100896or in the `BUCK` configuration file for Buck driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +0200897
David Pursehouse68153d72013-09-04 10:09:17 +0900898[source,python]
899----
900manifest_entries = [
901 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
902]
903----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200904
905If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
906capability check is done during the `UiAction` gathering, so the plugin author
907doesn't have to set `UiAction.Description.setVisible()` explicitly in this
908case.
909
910The following prerequisities must be met, to satisfy the capability check:
911
912* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100913* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +0200914* user is a member of a group which has the required capability
915
916The `apply` method is called when the button is clicked. If `UiAction` is
917combined with JavaScript API (its own JavaScript function is provided),
918then a popup dialog is normally opened to gather additional user input.
919A new button is placed on the popup dialog to actually send the request.
920
921Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
922can be accessed from any REST client, i. e.:
923
924====
925 curl -X POST -H "Content-Type: application/json" \
926 -d '{message: "François", french: true}' \
927 --digest --user joe:secret \
928 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
929 "Bonjour François from change 1, patch set 1!"
930====
931
David Pursehouse42245822013-09-24 09:48:20 +0900932A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100933particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200934
935[source,java]
936----
937public class Module extends AbstractModule {
938 @Override
939 protected void configure() {
940 install(new RestApiModule() {
941 @Override
942 protected void configure() {
943 delete(PROJECT_KIND)
944 .to(DeleteProject.class);
945 }
946 });
947 }
948}
949----
950
David Pursehouse42245822013-09-24 09:48:20 +0900951For a `UiAction` bound this way, a JS API function can be provided.
952
953Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200954can be bound per resource without view name. To define a JS function
955for the `UiAction`, "/" must be used as the name:
956
957[source,javascript]
958----
959Gerrit.install(function(self) {
960 function onDeleteProject(c) {
961 [...]
962 }
963 self.onAction('project', '/', onDeleteProject);
964});
965----
966
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +0200967[[top-menu-extensions]]
968Top Menu Extensions
969-------------------
970
971Plugins can contribute items to Gerrit's top menu.
972
973A single top menu extension can have multiple elements and will be put as
974the last element in Gerrit's top menu.
975
976Plugins define the top menu entries by implementing `TopMenu` interface:
977
978[source,java]
979----
980public class MyTopMenuExtension implements TopMenu {
981
982 @Override
983 public List<MenuEntry> getEntries() {
984 return Lists.newArrayList(
985 new MenuEntry("Top Menu Entry", Lists.newArrayList(
986 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
987 }
988}
989----
990
Edwin Kempin77f23242013-09-30 14:53:20 +0200991Plugins can also add additional menu items to Gerrit's top menu entries
992by defining a `MenuEntry` that has the same name as a Gerrit top menu
993entry:
994
995[source,java]
996----
997public class MyTopMenuExtension implements TopMenu {
998
999 @Override
1000 public List<MenuEntry> getEntries() {
1001 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001002 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001003 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1004 }
1005}
1006----
1007
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001008If no Guice modules are declared in the manifest, the top menu extension may use
1009auto-registration by providing an `@Listen` annotation:
1010
1011[source,java]
1012----
1013@Listen
1014public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001015 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001016}
1017----
1018
Luca Milanesiocb230402013-10-11 08:49:56 +01001019Otherwise the top menu extension must be bound in the plugin module used
1020for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001021
1022[source,java]
1023----
Luca Milanesiocb230402013-10-11 08:49:56 +01001024package com.googlesource.gerrit.plugins.helloworld;
1025
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001026public class HelloWorldModule extends AbstractModule {
1027 @Override
1028 protected void configure() {
1029 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1030 }
1031}
1032----
1033
Luca Milanesiocb230402013-10-11 08:49:56 +01001034[source,manifest]
1035----
1036Gerrit-ApiType: plugin
1037Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1038----
1039
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001040It is also possible to show some menu entries only if the user has a
1041certain capability:
1042
1043[source,java]
1044----
1045public class MyTopMenuExtension implements TopMenu {
1046 private final String pluginName;
1047 private final Provider<CurrentUser> userProvider;
1048 private final List<MenuEntry> menuEntries;
1049
1050 @Inject
1051 public MyTopMenuExtension(@PluginName String pluginName,
1052 Provider<CurrentUser> userProvider) {
1053 this.pluginName = pluginName;
1054 this.userProvider = userProvider;
1055 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1056
1057 // add menu entry that is only visible to users with a certain capability
1058 if (canSeeMenuEntry()) {
1059 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1060 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1061 }
1062
1063 // add menu entry that is visible to all users (even anonymous users)
1064 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1065 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1066 }
1067
1068 private boolean canSeeMenuEntry() {
1069 if (userProvider.get().isIdentifiedUser()) {
1070 CapabilityControl ctl = userProvider.get().getCapabilities();
1071 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1072 || ctl.canAdministrateServer();
1073 } else {
1074 return false;
1075 }
1076 }
1077
1078 @Override
1079 public List<MenuEntry> getEntries() {
1080 return menuEntries;
1081 }
1082}
1083----
1084
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001085[[gwt_ui_extension]]
1086GWT UI Extension
1087----------------
1088Plugins can extend the Gerrit UI with own GWT code.
1089
1090The Maven archetype 'gerrit-plugin-gwt-archetype' can be used to
1091generate a GWT plugin skeleton. How to use the Maven plugin archetypes
1092is described in the link:#getting-started[Getting started] section.
1093
1094The generated GWT plugin has a link:#top-menu-extensions[top menu] that
1095opens a GWT dialog box when the user clicks on it.
1096
Edwin Kempinf5a77332012-07-18 11:17:53 +02001097[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001098HTTP Servlets
1099-------------
1100
1101Plugins or extensions may register additional HTTP servlets, and
1102wrap them with HTTP filters.
1103
1104Servlets may use auto-registration to declare the URL they handle:
1105
David Pursehouse68153d72013-09-04 10:09:17 +09001106[source,java]
1107----
1108import com.google.gerrit.extensions.annotations.Export;
1109import com.google.inject.Singleton;
1110import javax.servlet.http.HttpServlet;
1111import javax.servlet.http.HttpServletRequest;
1112import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001113
David Pursehouse68153d72013-09-04 10:09:17 +09001114@Export("/print")
1115@Singleton
1116class HelloServlet extends HttpServlet {
1117 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
1118 res.setContentType("text/plain");
1119 res.setCharacterEncoding("UTF-8");
1120 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001121 }
David Pursehouse68153d72013-09-04 10:09:17 +09001122}
1123----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001124
Edwin Kempin8aa650f2012-07-18 11:25:48 +02001125The auto registration only works for standard servlet mappings like
1126`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
1127to register the HTTP servlets and declare it explicitly in the manifest
1128with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001129
David Pursehouse68153d72013-09-04 10:09:17 +09001130[source,java]
1131----
1132import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001133
David Pursehouse68153d72013-09-04 10:09:17 +09001134class MyWebUrls extends ServletModule {
1135 protected void configureServlets() {
1136 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001137 }
David Pursehouse68153d72013-09-04 10:09:17 +09001138}
1139----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001140
1141For a plugin installed as name `helloworld`, the servlet implemented
1142by HelloServlet class will be available to users as:
1143
1144----
1145$ curl http://review.example.com/plugins/helloworld/print
1146----
Nasser Grainawie033b262012-05-09 17:54:21 -07001147
Edwin Kempinf5a77332012-07-18 11:17:53 +02001148[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +02001149Data Directory
1150--------------
1151
1152Plugins can request a data directory with a `@PluginData` File
1153dependency. A data directory will be created automatically by the
1154server in `$site_path/data/$plugin_name` and passed to the plugin.
1155
1156Plugins can use this to store any data they want.
1157
David Pursehouse68153d72013-09-04 10:09:17 +09001158[source,java]
1159----
1160@Inject
1161MyType(@PluginData java.io.File myDir) {
1162 new FileInputStream(new File(myDir, "my.config"));
1163}
1164----
Edwin Kempin41f63912012-07-17 12:33:55 +02001165
Edwin Kempinea621482013-10-16 12:58:24 +02001166[[download-commands]]
1167Download Commands
1168-----------------
1169
1170Gerrit offers commands for downloading changes using different
1171download schemes (e.g. for downloading via different network
1172protocols). Plugins can contribute download schemes and download
1173commands by implementing
1174`com.google.gerrit.extensions.config.DownloadScheme` and
1175`com.google.gerrit.extensions.config.DownloadCommand`.
1176
1177The download schemes and download commands which are used most often
1178are provided by the Gerrit core plugin `download-commands`.
1179
Edwin Kempinf5a77332012-07-18 11:17:53 +02001180[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001181Documentation
1182-------------
1183
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001184If a plugin does not register a filter or servlet to handle URLs
1185`/Documentation/*` or `/static/*`, the core Gerrit server will
1186automatically export these resources over HTTP from the plugin JAR.
1187
David Pursehouse6853b5a2013-07-10 11:38:03 +09001188Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04001189available as `/plugins/helloworld/static/resource`. This prefix is
1190configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001191
David Pursehouse6853b5a2013-07-10 11:38:03 +09001192Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04001193will be available as `/plugins/helloworld/Documentation/resource`. This
1194prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
1195attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001196
1197Documentation may be written in
1198link:http://daringfireball.net/projects/markdown/[Markdown] style
1199if the file name ends with `.md`. Gerrit will automatically convert
1200Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07001201
Edwin Kempinf5a77332012-07-18 11:17:53 +02001202[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02001203Within the Markdown documentation files macros can be used that allow
1204to write documentation with reasonably accurate examples that adjust
1205automatically based on the installation.
1206
1207The following macros are supported:
1208
1209[width="40%",options="header"]
1210|===================================================
1211|Macro | Replacement
1212|@PLUGIN@ | name of the plugin
1213|@URL@ | Gerrit Web URL
1214|@SSH_HOST@ | SSH Host
1215|@SSH_PORT@ | SSH Port
1216|===================================================
1217
1218The macros will be replaced when the documentation files are rendered
1219from Markdown to HTML.
1220
1221Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
1222even if there is an expansion for `KEEP` in the future.
1223
Edwin Kempinf5a77332012-07-18 11:17:53 +02001224[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001225Automatic Index
1226~~~~~~~~~~~~~~~
1227
1228If a plugin does not handle its `/` URL itself, Gerrit will
1229redirect clients to the plugin's `/Documentation/index.html`.
1230Requests for `/Documentation/` (bare directory) will also redirect
1231to `/Documentation/index.html`.
1232
1233If neither resource `Documentation/index.html` or
1234`Documentation/index.md` exists in the plugin JAR, Gerrit will
1235automatically generate an index page for the plugin's documentation
1236tree by scanning every `*.md` and `*.html` file in the Documentation/
1237directory.
1238
1239For any discovered Markdown (`*.md`) file, Gerrit will parse the
1240header of the file and extract the first level one title. This
1241title text will be used as display text for a link to the HTML
1242version of the page.
1243
1244For any discovered HTML (`*.html`) file, Gerrit will use the name
1245of the file, minus the `*.html` extension, as the link text. Any
1246hyphens in the file name will be replaced with spaces.
1247
David Pursehouse6853b5a2013-07-10 11:38:03 +09001248If a discovered file is named `about.md` or `about.html`, its
1249content will be inserted in an 'About' section at the top of the
1250auto-generated index page. If both `about.md` and `about.html`
1251exist, only the first discovered file will be used.
1252
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001253If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09001254into a 'Commands' section of the generated index page.
1255
David Pursehousefe529152013-08-14 16:35:06 +09001256If a discovered file name beings with `servlet-` it will be clustered
1257into a 'Servlets' section of the generated index page.
1258
1259If a discovered file name beings with `rest-api-` it will be clustered
1260into a 'REST APIs' section of the generated index page.
1261
David Pursehouse6853b5a2013-07-10 11:38:03 +09001262All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001263
1264Some optional information from the manifest is extracted and
1265displayed as part of the index page, if present in the manifest:
1266
1267[width="40%",options="header"]
1268|===================================================
1269|Field | Source Attribute
1270|Name | Implementation-Title
1271|Vendor | Implementation-Vendor
1272|Version | Implementation-Version
1273|URL | Implementation-URL
1274|API Version | Gerrit-ApiVersion
1275|===================================================
1276
Edwin Kempinf5a77332012-07-18 11:17:53 +02001277[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001278Deployment
1279----------
1280
Edwin Kempinf7295742012-07-16 15:03:46 +02001281Compiled plugins and extensions can be deployed to a running Gerrit
1282server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001283
1284Plugins can also be copied directly into the server's
1285directory at `$site_path/plugins/$name.jar`. The name of
1286the JAR file, minus the `.jar` extension, will be used as the
1287plugin name. Unless disabled, servers periodically scan this
1288directory for updated plugins. The time can be adjusted by
1289link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001290
Edwin Kempinf7295742012-07-16 15:03:46 +02001291For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
1292command can be used.
1293
Brad Larsond5e87c32012-07-11 12:18:49 -05001294Disabled plugins can be re-enabled using the
1295link:cmd-plugin-enable.html[plugin enable] command.
1296
David Ostrovskyf86bae52013-09-01 09:10:39 +02001297SEE ALSO
1298--------
1299
1300* link:js-api.html[JavaScript API]
1301* link:dev-rest-api.html[REST API Developers' Notes]
1302
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001303GERRIT
1304------
1305Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07001306
1307SEARCHBOX
1308---------