Jenkins : Exposing data to the remote API

Remote API in Hudson

Hudson exposes various data through the remote API, and plugins can participate in this process to expose additional data.

Exposure of data is done by using annotations, @org.kohsuke.stapler.export.ExportedBean and @org.kohsuke.stapler.export.Exported . These annotations are used on a class and its fields/properties respectively, and through runtime reflection, the shape of the data is determined. For example,

@ExportedBean
class Person {
  @Exported
  public String name;
  @Exported
  public int getAge() {...};
  @Exported
  public Person getFather() { ... };
}

... could yield XML like this:

<person>
  <name>John Doe</name>
  <age>12</age>
  <father>
    <name>...</name>
    ...
  </father>
</person>

These annotations also work when inheritance is involved in the classes.

Exposing remote API from a model object

If your plugin is a hudson.model.ModelObject (or any other object that binds to URL), then you can expose the remote API by adding the following method on your class:

public Api getApi() { return new Api(this); }

You also need to put the @ExportedBean and @Exported annotations on your class and properties/fields for it to report anything meaningful.

You can see this code in many existing classes, such as hudson.model.Hudson. Through stapler, this creates an URL binding ".../api/" under the URL that your object is bound to, and it will serve the XML data rooted at your object (which is passed to the Api class in the constructor.)

Extending existing objects that are already exported

Some common extension points in Hudson, such as hudson.model.Action, are already exposed to the remote API through other objects (such as hudson.model.Actionable.getActions()) In such objects, you can have your data exposed by just putting @ExportedBean/@Exported annotations.

For example you can extend hudson.Plugin:

@ExportedBean
public class MyPlugin extends Plugin {

     public Api getApi() {
         return new Api(this);
     }

     @Exported
     public String getInformation() {
         return "some string";
     }
}

Let's pretend, that your plugin has the artifact-id my-special-plugin. Then "some string" would be available at "server/plugin/my-special-plugin/api/xml" - where server is the path to your running Jenkins instance. The XML would look like this:

<myPlugin>
  <information>some string</information>
</myPlugin>

Controlling the visibility of data

At the conceptual level, the remote API creates a graph of objects, where properties can be thought of as edges. An remote API call will have the 'root' object (which will be served as the top-level element in XML/JSON/etc), and a response contains a subset of this graph. As you can see in the user page, the caller of the API can use the depth parameter to control what subset is rendered, but your classes can also affect how this subset is determined.

The typical use case is that of the "-verbose" switch seen in many CLIs: you have some data that you think might be useful for some limited number of people, but because the data is bulky, you don't want to flood users who won't need them.

This can be done by using the @Exported.visibility() value in your annotation. When you set value to -1 or smaller, the marked property will be only rendered when a higher depth value is specified. The smaller the visbility() value is, the bigger depth value is required to render it. So use this annotation to expose more data liberally, without worrying about over flooding casual consumers. Similarly, if you specify a bigger visibility value, you can make the property that much more visible.

A similar but different use case is when you have some data in a separate object), but you consider that to be really important that you always want to render it. Normally, traversing to the referenced object gets you that much closer to the subtree cut-off, but by adding @Exported(inline=true), you can avoid this. Do so when the object you are referencing is logically the same data that your object itself is trying to display. Use of this can be seen in places like hudson.model.User.getAllProperties(), where the properties of the User object are teared off to other objects.