| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 1 | Gerrit Code Review - Plugin Development | 
 | 2 | ======================================= | 
 | 3 |  | 
| Edwin Kempin | af27532 | 2012-07-16 11:04:01 +0200 | [diff] [blame] | 4 | The Gerrit server functionality can be extended by installing plugins. | 
 | 5 | This page describes how plugins for Gerrit can be developed. | 
 | 6 |  | 
 | 7 | Depending on how tightly the extension code is coupled with the Gerrit | 
 | 8 | server code, there is a distinction between `plugins` and `extensions`. | 
 | 9 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 10 | [[plugin]] | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 11 | A `plugin` in Gerrit is tightly coupled code that runs in the same | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 12 | JVM as Gerrit. It has full access to all server internals. Plugins | 
 | 13 | are tightly coupled to a specific major.minor server version and | 
 | 14 | may require source code changes to compile against a different | 
 | 15 | server version. | 
 | 16 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 17 | [[extension]] | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 18 | An `extension` in Gerrit runs inside of the same JVM as Gerrit | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 19 | in the same way as a plugin, but has limited visibility to the | 
| Edwin Kempin | fd19bfb | 2012-07-16 10:44:17 +0200 | [diff] [blame] | 20 | server's internals. The limited visibility reduces the extension's | 
 | 21 | dependencies, enabling it to be compatible across a wider range | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 22 | of server versions. | 
 | 23 |  | 
 | 24 | Most of this documentation refers to either type as a plugin. | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 25 |  | 
| Edwin Kempin | f878c4b | 2012-07-18 09:34:25 +0200 | [diff] [blame] | 26 | [[getting-started]] | 
 | 27 | Getting started | 
 | 28 | --------------- | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 29 |  | 
| Edwin Kempin | f878c4b | 2012-07-18 09:34:25 +0200 | [diff] [blame] | 30 | To get started with the development of a plugin there are two | 
 | 31 | recommended ways: | 
| Dave Borowitz | 5cc8f66 | 2012-05-21 09:51:36 -0700 | [diff] [blame] | 32 |  | 
| Edwin Kempin | f878c4b | 2012-07-18 09:34:25 +0200 | [diff] [blame] | 33 | . use the Gerrit Plugin Maven archetype to create a new plugin project: | 
 | 34 | + | 
 | 35 | With the Gerrit Plugin Maven archetype you can create a skeleton for a | 
 | 36 | plugin project. | 
 | 37 | + | 
 | 38 | ---- | 
 | 39 | mvn 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 | + | 
 | 46 | Maven will ask for additional properties and then create the plugin in | 
 | 47 | the current directory. To change the default property values answer 'n' | 
 | 48 | when Maven asks to confirm the properties configuration. It will then | 
 | 49 | ask again for all properties including those with predefined default | 
 | 50 | values. | 
 | 51 |  | 
 | 52 | . clone the sample helloworld plugin: | 
 | 53 | + | 
 | 54 | This is a Maven project that adds an SSH command to Gerrit to print | 
 | 55 | out a hello world message. It can be taken as an example to develop | 
 | 56 | an own plugin. | 
 | 57 | + | 
| Dave Borowitz | 5cc8f66 | 2012-05-21 09:51:36 -0700 | [diff] [blame] | 58 | ---- | 
 | 59 | $ git clone https://gerrit.googlesource.com/plugins/helloworld | 
 | 60 | ---- | 
| Edwin Kempin | f878c4b | 2012-07-18 09:34:25 +0200 | [diff] [blame] | 61 | + | 
 | 62 | When starting from this example one should take care to adapt the | 
 | 63 | `Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which | 
 | 64 | the plugin is developed. If the plugin is developed for a released | 
 | 65 | Gerrit version (no `SNAPSHOT` version) then the URL for the | 
 | 66 | `gerrit-api-repository` in the `pom.xml` needs to be changed to | 
| Shawn Pearce | d500500 | 2013-06-21 11:01:45 -0700 | [diff] [blame] | 67 | `https://gerrit-api.storage.googleapis.com/release/`. | 
| Dave Borowitz | 5cc8f66 | 2012-05-21 09:51:36 -0700 | [diff] [blame] | 68 |  | 
| Edwin Kempin | f878c4b | 2012-07-18 09:34:25 +0200 | [diff] [blame] | 69 | [[API]] | 
 | 70 | API | 
 | 71 | --- | 
 | 72 |  | 
 | 73 | There are two different API formats offered against which plugins can | 
 | 74 | be developed: | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 75 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 76 | gerrit-extension-api.jar:: | 
 | 77 |  A stable but thin interface. Suitable for extensions that need | 
 | 78 |  to be notified of events, but do not require tight coupling to | 
 | 79 |  the internals of Gerrit. Extensions built against this API can | 
 | 80 |  expect to be binary compatible across a wide range of server | 
 | 81 |  versions. | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 82 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 83 | gerrit-plugin-api.jar:: | 
 | 84 |  The complete internals of the Gerrit server, permitting a | 
 | 85 |  plugin to tightly couple itself and provide additional | 
 | 86 |  functionality that is not possible as an extension. Plugins | 
 | 87 |  built against this API are expected to break at the source | 
 | 88 |  code level between every major.minor Gerrit release. A plugin | 
 | 89 |  that compiles against 2.5 will probably need source code level | 
 | 90 |  changes to work with 2.6, 2.7, and so on. | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 91 |  | 
 | 92 | Manifest | 
 | 93 | -------- | 
 | 94 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 95 | Plugins may provide optional description information with standard | 
 | 96 | manifest fields: | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 97 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 98 | ==== | 
 | 99 |  Implementation-Title: Example plugin showing examples | 
 | 100 |  Implementation-Version: 1.0 | 
 | 101 |  Implementation-Vendor: Example, Inc. | 
 | 102 |  Implementation-URL: http://example.com/opensource/plugin-foo/ | 
 | 103 | ==== | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 104 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 105 | ApiType | 
 | 106 | ~~~~~~~ | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 107 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 108 | Plugins using the tightly coupled `gerrit-plugin-api.jar` must | 
 | 109 | declare this API dependency in the manifest to gain access to server | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 110 | internals. If no `Gerrit-ApiType` is specified the stable `extension` | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 111 | API will be assumed. This may cause ClassNotFoundExceptions when | 
 | 112 | loading a plugin that needs the plugin API. | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 113 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 114 | ==== | 
 | 115 |  Gerrit-ApiType: plugin | 
 | 116 | ==== | 
 | 117 |  | 
 | 118 | Explicit Registration | 
 | 119 | ~~~~~~~~~~~~~~~~~~~~~ | 
 | 120 |  | 
 | 121 | Plugins that use explicit Guice registration must name the Guice | 
 | 122 | modules in the manifest. Up to three modules can be named in the | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 123 | manifest. `Gerrit-Module` supplies bindings to the core server; | 
 | 124 | `Gerrit-SshModule` supplies SSH commands to the SSH server (if | 
 | 125 | enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 126 | server (if enabled). If no modules are named automatic registration | 
 | 127 | will be performed by scanning all classes in the plugin JAR for | 
 | 128 | `@Listen` and `@Export("")` annotations. | 
 | 129 |  | 
 | 130 | ==== | 
 | 131 |  Gerrit-Module: tld.example.project.CoreModuleClassName | 
 | 132 |  Gerrit-SshModule: tld.example.project.SshModuleClassName | 
 | 133 |  Gerrit-HttpModule: tld.example.project.HttpModuleClassName | 
 | 134 | ==== | 
 | 135 |  | 
| Edwin Kempin | f729574 | 2012-07-16 15:03:46 +0200 | [diff] [blame] | 136 | [[reload_method]] | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 137 | Reload Method | 
 | 138 | ~~~~~~~~~~~~~ | 
 | 139 |  | 
 | 140 | If a plugin holds an exclusive resource that must be released before | 
 | 141 | loading the plugin again (for example listening on a network port or | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 142 | acquiring a file lock) the manifest must declare `Gerrit-ReloadMode` | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 143 | to be `restart`. Otherwise the preferred method of `reload` will | 
 | 144 | be used, as it enables the server to hot-patch an updated plugin | 
 | 145 | with no down time. | 
 | 146 |  | 
 | 147 | ==== | 
 | 148 |  Gerrit-ReloadMode: restart | 
 | 149 | ==== | 
 | 150 |  | 
 | 151 | In either mode ('restart' or 'reload') any plugin or extension can | 
 | 152 | be updated without restarting the Gerrit server. The difference is | 
 | 153 | how Gerrit handles the upgrade: | 
 | 154 |  | 
 | 155 | restart:: | 
 | 156 |  The old plugin is completely stopped. All registrations of SSH | 
 | 157 |  commands and HTTP servlets are removed. All registrations of any | 
 | 158 |  extension points are removed. All registered LifecycleListeners | 
 | 159 |  have their `stop()` method invoked in reverse order. The new | 
 | 160 |  plugin is started, and registrations are made from the new | 
 | 161 |  plugin. There is a brief window where neither the old nor the | 
 | 162 |  new plugin is connected to the server. This means SSH commands | 
 | 163 |  and HTTP servlets will return not found errors, and the plugin | 
 | 164 |  will not be notified of events that occurred during the restart. | 
 | 165 |  | 
 | 166 | reload:: | 
 | 167 |  The new plugin is started. Its LifecycleListeners are permitted | 
 | 168 |  to perform their `start()` methods. All SSH and HTTP registrations | 
 | 169 |  are atomically swapped out from the old plugin to the new plugin, | 
 | 170 |  ensuring the server never returns a not found error. All extension | 
 | 171 |  point listeners are atomically swapped out from the old plugin to | 
 | 172 |  the new plugin, ensuring no events are missed (however some events | 
 | 173 |  may still route to the old plugin if the swap wasn't complete yet). | 
 | 174 |  The old plugin is stopped. | 
 | 175 |  | 
| Edwin Kempin | f729574 | 2012-07-16 15:03:46 +0200 | [diff] [blame] | 176 | To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload] | 
 | 177 | command can be used. | 
 | 178 |  | 
| Luca Milanesio | 737285d | 2012-09-25 14:26:43 +0100 | [diff] [blame] | 179 | [[init_step]] | 
 | 180 | Init step | 
 | 181 | ~~~~~~~~~ | 
 | 182 |  | 
 | 183 | Plugins can contribute their own "init step" during the Gerrit init | 
 | 184 | wizard. This is useful for guiding the Gerrit administrator through | 
 | 185 | the settings needed by the plugin to work propertly. | 
 | 186 |  | 
 | 187 | For instance plugins to integrate Jira issues to Gerrit changes may | 
 | 188 | contribute their own "init step" to allow configuring the Jira URL, | 
 | 189 | credentials and possibly verify connectivity to validate them. | 
 | 190 |  | 
 | 191 | ==== | 
 | 192 |  Gerrit-InitStep: tld.example.project.MyInitStep | 
 | 193 | ==== | 
 | 194 |  | 
 | 195 | MyInitStep needs to follow the standard Gerrit InitStep syntax | 
| David Pursehouse | 9246356 | 2013-06-24 10:16:28 +0900 | [diff] [blame] | 196 | and behavior: writing to the console using the injected ConsoleUI | 
| Luca Milanesio | 737285d | 2012-09-25 14:26:43 +0100 | [diff] [blame] | 197 | and accessing / changing configuration settings using Section.Factory. | 
 | 198 |  | 
 | 199 | In addition to the standard Gerrit init injections, plugins receive | 
 | 200 | the @PluginName String injection containing their own plugin name. | 
 | 201 |  | 
 | 202 | Bear in mind that the Plugin's InitStep class will be loaded but | 
 | 203 | the standard Gerrit runtime environment is not available and the plugin's | 
 | 204 | own Guice modules were not initialized. | 
 | 205 | This means the InitStep for a plugin is not executed in the same way that | 
 | 206 | the plugin executes within the server, and may mean a plugin author cannot | 
 | 207 | trivially reuse runtime code during init. | 
 | 208 |  | 
 | 209 | For instance a plugin that wants to verify connectivity may need to statically | 
 | 210 | call the constructor of their connection class, passing in values obtained | 
 | 211 | from the Section.Factory rather than from an injected Config object. | 
 | 212 |  | 
 | 213 | Plugins InitStep are executing during the "Gerrit Plugin init" phase, after | 
 | 214 | the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins | 
 | 215 | and before the DB Schema initialization or upgrade. | 
 | 216 | Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime | 
 | 217 | objects injected at startup. | 
 | 218 |  | 
 | 219 | ==== | 
| Sasa Zivkov | 69f99fb | 2013-06-24 16:54:15 +0200 | [diff] [blame] | 220 |  public class MyInitStep implements InitStep { | 
 | 221 |  private final ConsoleUI ui; | 
 | 222 |  private final Section.Factory sections; | 
 | 223 |  private final String pluginName; | 
| Luca Milanesio | 737285d | 2012-09-25 14:26:43 +0100 | [diff] [blame] | 224 |  | 
| Sasa Zivkov | 69f99fb | 2013-06-24 16:54:15 +0200 | [diff] [blame] | 225 |  @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; | 
 | 230 |  } | 
 | 231 |  | 
 | 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 |  } | 
| Luca Milanesio | 737285d | 2012-09-25 14:26:43 +0100 | [diff] [blame] | 239 |  } | 
| Luca Milanesio | 737285d | 2012-09-25 14:26:43 +0100 | [diff] [blame] | 240 | ==== | 
 | 241 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 242 | [[classpath]] | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 243 | Classpath | 
 | 244 | --------- | 
 | 245 |  | 
 | 246 | Each plugin is loaded into its own ClassLoader, isolating plugins | 
 | 247 | from each other. A plugin or extension inherits the Java runtime | 
 | 248 | and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin) | 
 | 249 | from the hosting server. | 
 | 250 |  | 
 | 251 | Plugins are loaded from a single JAR file. If a plugin needs | 
 | 252 | additional libraries, it must include those dependencies within | 
 | 253 | its own JAR. Plugins built using Maven may be able to use the | 
 | 254 | link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin] | 
 | 255 | to package additional dependencies. Relocating (or renaming) classes | 
 | 256 | should not be necessary due to the ClassLoader isolation. | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 257 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 258 | [[ssh]] | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 259 | SSH Commands | 
 | 260 | ------------ | 
 | 261 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 262 | Plugins may provide commands that can be accessed through the SSH | 
 | 263 | interface (extensions do not have this option). | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 264 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 265 | Command implementations must extend the base class SshCommand: | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 266 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 267 | ==== | 
 | 268 |  import com.google.gerrit.sshd.SshCommand; | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 269 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 270 |  class PrintHello extends SshCommand { | 
 | 271 |  protected abstract void run() { | 
 | 272 |  stdout.print("Hello\n"); | 
 | 273 |  } | 
 | 274 |  } | 
 | 275 | ==== | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 276 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 277 | If no Guice modules are declared in the manifest, SSH commands may | 
| Edwin Kempin | 948de0f | 2012-07-16 10:34:35 +0200 | [diff] [blame] | 278 | use auto-registration by providing an `@Export` annotation: | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 279 |  | 
 | 280 | ==== | 
 | 281 |  import com.google.gerrit.extensions.annotations.Export; | 
 | 282 |  import com.google.gerrit.sshd.SshCommand; | 
 | 283 |  | 
 | 284 |  @Export("print") | 
 | 285 |  class PrintHello extends SshCommand { | 
 | 286 |  protected abstract void run() { | 
 | 287 |  stdout.print("Hello\n"); | 
 | 288 |  } | 
 | 289 |  } | 
 | 290 | ==== | 
 | 291 |  | 
 | 292 | If explicit registration is being used, a Guice module must be | 
 | 293 | supplied to register the SSH command and declared in the manifest | 
 | 294 | with the `Gerrit-SshModule` attribute: | 
 | 295 |  | 
 | 296 | ==== | 
 | 297 |  import com.google.gerrit.sshd.PluginCommandModule; | 
 | 298 |  | 
 | 299 |  class MyCommands extends PluginCommandModule { | 
 | 300 |  protected void configureCommands() { | 
 | 301 |  command("print").to(PrintHello.class); | 
 | 302 |  } | 
 | 303 |  } | 
 | 304 | ==== | 
 | 305 |  | 
 | 306 | For a plugin installed as name `helloworld`, the command implemented | 
 | 307 | by PrintHello class will be available to users as: | 
 | 308 |  | 
 | 309 | ---- | 
| Keunhong Park | a09a6f1 | 2012-07-10 14:45:02 -0600 | [diff] [blame] | 310 | $ ssh -p 29418 review.example.com helloworld print | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 311 | ---- | 
 | 312 |  | 
| David Ostrovsky | 7066cc0 | 2013-06-15 14:46:23 +0200 | [diff] [blame] | 313 | [[capabilities]] | 
 | 314 | Plugin Owned Capabilities | 
 | 315 | ------------------------- | 
 | 316 |  | 
 | 317 | Plugins may provide their own capabilities and restrict usage of SSH | 
 | 318 | commands to the users who are granted those capabilities. | 
 | 319 |  | 
 | 320 | Plugins define the capabilities by overriding the `CapabilityDefinition` | 
 | 321 | abstract class: | 
 | 322 |  | 
 | 323 | ==== | 
 | 324 |  public class PrintHelloCapability extends CapabilityDefinition { | 
 | 325 |  @Override | 
 | 326 |  public String getDescription() { | 
 | 327 |  return "Print Hello"; | 
 | 328 |  } | 
 | 329 |  } | 
 | 330 | ==== | 
 | 331 |  | 
 | 332 | If no Guice modules are declared in the manifest, UI commands may | 
 | 333 | use auto-registration by providing an `@Export` annotation: | 
 | 334 |  | 
 | 335 | ==== | 
 | 336 |  @Export("printHello") | 
 | 337 |  public class PrintHelloCapability extends CapabilityDefinition { | 
 | 338 |  ... | 
 | 339 | ==== | 
 | 340 |  | 
 | 341 | Otherwise the capability must be bound in a plugin module: | 
 | 342 |  | 
 | 343 | ==== | 
 | 344 |  public class HelloWorldModule extends AbstractModule { | 
 | 345 |  @Override | 
 | 346 |  protected void configure() { | 
 | 347 |  bind(CapabilityDefinition.class) | 
 | 348 |  .annotatedWith(Exports.named("printHello")) | 
 | 349 |  .to(PrintHelloCapability.class); | 
 | 350 |  } | 
 | 351 |  } | 
 | 352 | ==== | 
 | 353 |  | 
 | 354 | With a plugin-owned capability defined in this way, it is possible to restrict | 
 | 355 | usage of an SSH command or UiAction to members of the group that were granted | 
 | 356 | this capability in the usual way, using the `RequiresCapability` annotation: | 
 | 357 |  | 
 | 358 | ==== | 
 | 359 |  @RequiresCapability("printHello") | 
| Shawn Pearce | 5b70c22 | 2013-08-12 19:49:41 -0700 | [diff] [blame] | 360 |  @CommandMetaData(name="print", description="Print greeting in different languages") | 
| David Ostrovsky | 7066cc0 | 2013-06-15 14:46:23 +0200 | [diff] [blame] | 361 |  public final class PrintHelloWorldCommand extends SshCommand { | 
 | 362 |  ... | 
 | 363 | ==== | 
 | 364 |  | 
 | 365 | Or with UiAction: | 
 | 366 |  | 
 | 367 | ==== | 
 | 368 |  @RequiresCapability("printHello") | 
 | 369 |  public class SayHelloAction extends UiAction<RevisionResource> | 
 | 370 |  implements RestModifyView<RevisionResource, SayHelloAction.Input> { | 
 | 371 |  ... | 
 | 372 | ==== | 
 | 373 |  | 
 | 374 | Capability scope was introduced to differentiate between plugin-owned | 
 | 375 | capabilities and core capabilities. Per default the scope of | 
 | 376 | @RequiresCapability annotation is `CapabilityScope.CONTEXT`, that means: | 
 | 377 | + | 
 | 378 | * when `@RequiresCapability` is used within a plugin the scope of the | 
 | 379 | capability is assumed to be that plugin. | 
 | 380 | + | 
 | 381 | * If `@RequiresCapability` is used within the core Gerrit Code Review server | 
 | 382 | (and thus is outside of a plugin) the scope is the core server and will use | 
 | 383 | the `GlobalCapability` known to Gerrit Code Review server. | 
 | 384 |  | 
 | 385 | If a plugin needs to use a core capability name (e.g. "administrateServer") | 
 | 386 | this can be specified by setting `scope = CapabilityScope.CORE`: | 
 | 387 |  | 
 | 388 | ==== | 
 | 389 |  @RequiresCapability(value = "administrateServer", scope = | 
 | 390 |  CapabilityScope.CORE) | 
 | 391 |  ... | 
 | 392 | ==== | 
 | 393 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 394 | [[http]] | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 395 | HTTP Servlets | 
 | 396 | ------------- | 
 | 397 |  | 
 | 398 | Plugins or extensions may register additional HTTP servlets, and | 
 | 399 | wrap them with HTTP filters. | 
 | 400 |  | 
 | 401 | Servlets may use auto-registration to declare the URL they handle: | 
 | 402 |  | 
 | 403 | ==== | 
 | 404 |  import com.google.gerrit.extensions.annotations.Export; | 
 | 405 |  import com.google.inject.Singleton; | 
 | 406 |  import javax.servlet.http.HttpServlet; | 
 | 407 |  import javax.servlet.http.HttpServletRequest; | 
 | 408 |  import javax.servlet.http.HttpServletResponse; | 
 | 409 |  | 
 | 410 |  @Export("/print") | 
 | 411 |  @Singleton | 
 | 412 |  class HelloServlet extends HttpServlet { | 
 | 413 |  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { | 
 | 414 |  res.setContentType("text/plain"); | 
 | 415 |  res.setCharacterEncoding("UTF-8"); | 
 | 416 |  res.getWriter().write("Hello"); | 
 | 417 |  } | 
 | 418 |  } | 
 | 419 | ==== | 
 | 420 |  | 
| Edwin Kempin | 8aa650f | 2012-07-18 11:25:48 +0200 | [diff] [blame] | 421 | The auto registration only works for standard servlet mappings like | 
 | 422 | `/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule | 
 | 423 | to register the HTTP servlets and declare it explicitly in the manifest | 
 | 424 | with the `Gerrit-HttpModule` attribute: | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 425 |  | 
 | 426 | ==== | 
 | 427 |  import com.google.inject.servlet.ServletModule; | 
 | 428 |  | 
 | 429 |  class MyWebUrls extends ServletModule { | 
 | 430 |  protected void configureServlets() { | 
 | 431 |  serve("/print").with(HelloServlet.class); | 
 | 432 |  } | 
 | 433 |  } | 
 | 434 | ==== | 
 | 435 |  | 
 | 436 | For a plugin installed as name `helloworld`, the servlet implemented | 
 | 437 | by HelloServlet class will be available to users as: | 
 | 438 |  | 
 | 439 | ---- | 
 | 440 | $ curl http://review.example.com/plugins/helloworld/print | 
 | 441 | ---- | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 442 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 443 | [[data-directory]] | 
| Edwin Kempin | 41f6391 | 2012-07-17 12:33:55 +0200 | [diff] [blame] | 444 | Data Directory | 
 | 445 | -------------- | 
 | 446 |  | 
 | 447 | Plugins can request a data directory with a `@PluginData` File | 
 | 448 | dependency. A data directory will be created automatically by the | 
 | 449 | server in `$site_path/data/$plugin_name` and passed to the plugin. | 
 | 450 |  | 
 | 451 | Plugins can use this to store any data they want. | 
 | 452 |  | 
 | 453 | ==== | 
 | 454 |  @Inject | 
 | 455 |  MyType(@PluginData java.io.File myDir) { | 
 | 456 |  new FileInputStream(new File(myDir, "my.config")); | 
 | 457 |  } | 
 | 458 | ==== | 
 | 459 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 460 | [[documentation]] | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 461 | Documentation | 
 | 462 | ------------- | 
 | 463 |  | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 464 | If a plugin does not register a filter or servlet to handle URLs | 
 | 465 | `/Documentation/*` or `/static/*`, the core Gerrit server will | 
 | 466 | automatically export these resources over HTTP from the plugin JAR. | 
 | 467 |  | 
| David Pursehouse | 6853b5a | 2013-07-10 11:38:03 +0900 | [diff] [blame] | 468 | Static resources under the `static/` directory in the JAR will be | 
| Dave Borowitz | b893ac8 | 2013-03-27 10:03:55 -0400 | [diff] [blame] | 469 | available as `/plugins/helloworld/static/resource`. This prefix is | 
 | 470 | configurable by setting the `Gerrit-HttpStaticPrefix` attribute. | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 471 |  | 
| David Pursehouse | 6853b5a | 2013-07-10 11:38:03 +0900 | [diff] [blame] | 472 | Documentation files under the `Documentation/` directory in the JAR | 
| Dave Borowitz | b893ac8 | 2013-03-27 10:03:55 -0400 | [diff] [blame] | 473 | will be available as `/plugins/helloworld/Documentation/resource`. This | 
 | 474 | prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix` | 
 | 475 | attribute. | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 476 |  | 
 | 477 | Documentation may be written in | 
 | 478 | link:http://daringfireball.net/projects/markdown/[Markdown] style | 
 | 479 | if the file name ends with `.md`. Gerrit will automatically convert | 
 | 480 | Markdown to HTML if accessed with extension `.html`. | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 481 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 482 | [[macros]] | 
| Edwin Kempin | c78777d | 2012-07-16 15:55:11 +0200 | [diff] [blame] | 483 | Within the Markdown documentation files macros can be used that allow | 
 | 484 | to write documentation with reasonably accurate examples that adjust | 
 | 485 | automatically based on the installation. | 
 | 486 |  | 
 | 487 | The following macros are supported: | 
 | 488 |  | 
 | 489 | [width="40%",options="header"] | 
 | 490 | |=================================================== | 
 | 491 | |Macro | Replacement | 
 | 492 | |@PLUGIN@ | name of the plugin | 
 | 493 | |@URL@ | Gerrit Web URL | 
 | 494 | |@SSH_HOST@ | SSH Host | 
 | 495 | |@SSH_PORT@ | SSH Port | 
 | 496 | |=================================================== | 
 | 497 |  | 
 | 498 | The macros will be replaced when the documentation files are rendered | 
 | 499 | from Markdown to HTML. | 
 | 500 |  | 
 | 501 | Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@` | 
 | 502 | even if there is an expansion for `KEEP` in the future. | 
 | 503 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 504 | [[auto-index]] | 
| Shawn O. Pearce | 795167c | 2012-05-12 11:20:18 -0700 | [diff] [blame] | 505 | Automatic Index | 
 | 506 | ~~~~~~~~~~~~~~~ | 
 | 507 |  | 
 | 508 | If a plugin does not handle its `/` URL itself, Gerrit will | 
 | 509 | redirect clients to the plugin's `/Documentation/index.html`. | 
 | 510 | Requests for `/Documentation/` (bare directory) will also redirect | 
 | 511 | to `/Documentation/index.html`. | 
 | 512 |  | 
 | 513 | If neither resource `Documentation/index.html` or | 
 | 514 | `Documentation/index.md` exists in the plugin JAR, Gerrit will | 
 | 515 | automatically generate an index page for the plugin's documentation | 
 | 516 | tree by scanning every `*.md` and `*.html` file in the Documentation/ | 
 | 517 | directory. | 
 | 518 |  | 
 | 519 | For any discovered Markdown (`*.md`) file, Gerrit will parse the | 
 | 520 | header of the file and extract the first level one title. This | 
 | 521 | title text will be used as display text for a link to the HTML | 
 | 522 | version of the page. | 
 | 523 |  | 
 | 524 | For any discovered HTML (`*.html`) file, Gerrit will use the name | 
 | 525 | of the file, minus the `*.html` extension, as the link text. Any | 
 | 526 | hyphens in the file name will be replaced with spaces. | 
 | 527 |  | 
| David Pursehouse | 6853b5a | 2013-07-10 11:38:03 +0900 | [diff] [blame] | 528 | If a discovered file is named `about.md` or `about.html`, its | 
 | 529 | content will be inserted in an 'About' section at the top of the | 
 | 530 | auto-generated index page. If both `about.md` and `about.html` | 
 | 531 | exist, only the first discovered file will be used. | 
 | 532 |  | 
| Shawn O. Pearce | 795167c | 2012-05-12 11:20:18 -0700 | [diff] [blame] | 533 | If a discovered file name beings with `cmd-` it will be clustered | 
| David Pursehouse | 6853b5a | 2013-07-10 11:38:03 +0900 | [diff] [blame] | 534 | into a 'Commands' section of the generated index page. | 
 | 535 |  | 
| David Pursehouse | fe52915 | 2013-08-14 16:35:06 +0900 | [diff] [blame^] | 536 | If a discovered file name beings with `servlet-` it will be clustered | 
 | 537 | into a 'Servlets' section of the generated index page. | 
 | 538 |  | 
 | 539 | If a discovered file name beings with `rest-api-` it will be clustered | 
 | 540 | into a 'REST APIs' section of the generated index page. | 
 | 541 |  | 
| David Pursehouse | 6853b5a | 2013-07-10 11:38:03 +0900 | [diff] [blame] | 542 | All other files are clustered under a 'Documentation' section. | 
| Shawn O. Pearce | 795167c | 2012-05-12 11:20:18 -0700 | [diff] [blame] | 543 |  | 
 | 544 | Some optional information from the manifest is extracted and | 
 | 545 | displayed as part of the index page, if present in the manifest: | 
 | 546 |  | 
 | 547 | [width="40%",options="header"] | 
 | 548 | |=================================================== | 
 | 549 | |Field | Source Attribute | 
 | 550 | |Name | Implementation-Title | 
 | 551 | |Vendor | Implementation-Vendor | 
 | 552 | |Version | Implementation-Version | 
 | 553 | |URL | Implementation-URL | 
 | 554 | |API Version | Gerrit-ApiVersion | 
 | 555 | |=================================================== | 
 | 556 |  | 
| Edwin Kempin | f5a7733 | 2012-07-18 11:17:53 +0200 | [diff] [blame] | 557 | [[deployment]] | 
| Nasser Grainawi | e033b26 | 2012-05-09 17:54:21 -0700 | [diff] [blame] | 558 | Deployment | 
 | 559 | ---------- | 
 | 560 |  | 
| Edwin Kempin | f729574 | 2012-07-16 15:03:46 +0200 | [diff] [blame] | 561 | Compiled plugins and extensions can be deployed to a running Gerrit | 
 | 562 | server using the link:cmd-plugin-install.html[plugin install] command. | 
| Shawn O. Pearce | da4919a | 2012-05-10 16:54:28 -0700 | [diff] [blame] | 563 |  | 
 | 564 | Plugins can also be copied directly into the server's | 
 | 565 | directory at `$site_path/plugins/$name.jar`. The name of | 
 | 566 | the JAR file, minus the `.jar` extension, will be used as the | 
 | 567 | plugin name. Unless disabled, servers periodically scan this | 
 | 568 | directory for updated plugins. The time can be adjusted by | 
 | 569 | link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency]. | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 570 |  | 
| Edwin Kempin | f729574 | 2012-07-16 15:03:46 +0200 | [diff] [blame] | 571 | For disabling plugins the link:cmd-plugin-remove.html[plugin remove] | 
 | 572 | command can be used. | 
 | 573 |  | 
| Brad Larson | d5e87c3 | 2012-07-11 12:18:49 -0500 | [diff] [blame] | 574 | Disabled plugins can be re-enabled using the | 
 | 575 | link:cmd-plugin-enable.html[plugin enable] command. | 
 | 576 |  | 
| Deniz Türkoglu | eb78b60 | 2012-05-07 14:02:36 -0700 | [diff] [blame] | 577 | GERRIT | 
 | 578 | ------ | 
 | 579 | Part of link:index.html[Gerrit Code Review] |