Just as Jenkins core defines a set of extension points, your plugin can define new extension points so that other plugins can contribute implementations. This page shows how to do that.
Extension points may be defined in either of two ways:
- Singleton pattern
- Describable/Descriptor pattern (builds on singleton pattern)
Singleton Pattern
First, you define your contract in terms of an abstract class. (You can do this in interfaces, too, but then you'll have hard time adding new methods in later versions of your plugin.) This class should implement the marker hudson.ExtensionPoint interface, to designate that it is an extension point.
For convenience you can also define a static method conventionally named all
. The returned ExtensionList
allows you discover all the implementations at runtime.
The following code shows this in a concrete example:
/** * Extension point that defines different kinds of animals */ public abstract class Animal implements ExtensionPoint { ... /** * All registered {@link Animal}s. */ public static ExtensionList<Animal> all() { return Jenkins.getActiveInstance().getExtensionList(Animal.class); // getActiveInstance() starting with Jenkins 1.590, else getInstance() } }
In addition to these basic ingredients, your extension point can implement additional interfaces, extend from another base class, define all sorts of methods, etc. Because the extension implementations will be singletons, usually the default no-argument constructor suffices.
Also, this system is only necessary when you need to discover the implementations at runtime. So if your extension point consists of a group of several classes and interfaces, normally only the entry point needs to follow this convention. Such an example can be seen in hudson.scm.ChangeLogParser in the core — ChangeLogParser
implementations are always created by hudson.scm.SCM implementations, so ChangeLogParser
is not an extension point.
Enumerating implementations at runtime
The following code shows how you can list up all the Animal
implementations contributed by other plugins.
for (Animal a : Animal.all()) { System.out.println(a); }
There are other convenience methods to find a particular instance, and so on. See hudson.ExtensionList for more details.
Implementing extension points
Implementing an extension point defined in a plugin is no different from implementing an extension point defined in the core. See hudson.Extension for more details.
@Extension public class Lion extends Animal { ... }
Describable/Descriptor pattern
If you are going to define a new extension point that follows the hudson.model.Describable/hudson.model.Descriptor pattern, the convention is bit different. This pattern is used when users should be able to configure zero or more instances of some things you define.
In this case the singleton is a Descriptor
: a description of the kind of thing a user can create and configure. There is a matching Describable
class which represents the actual things being created and configured. Confusingly, the convention is to place the ExtensionPoint
marker on the Describable
class, even though @Extension
will be put on implementations of the Descriptor
.
For this, first you define your Describable
subtype and Descriptor
subtype.
public abstract class Food extends AbstractDescribable<Food> implements ExtensionPoint { ... @Override public FoodDescriptor getDescriptor() { return (FoodDescriptor) super.getDescriptor(); } } public abstract class FoodDescriptor extends Descriptor<Food> { protected FoodDescriptor() { } // optional constructor specifying class (the default one usually suffices however): protected FoodDescriptor(Class<? extends Food> clazz) { super(clazz); } }
An extension for Food would look like this:
public class MyFoodImpl extends Food { private final boolean tasty; @DataBoundConstructor public MyFoodImpl(boolean tasty) { this.tasty = tasty; } public boolean isTasty() {return tasty;} ... @Extension public static class DescriptorImpl extends FoodDescriptor { @Override public String getDisplayName() { return "MyFood"; } } }
Typically you will also need a src/main/resources/…/MyFoodImpl/config.jelly
providing its configuration form. The exact Jelly views needed for a describable vary according to how the extension point is used.