Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Working Directly With The Native Java Classes

jcmuller edited this page Apr 11, 2012 · 4 revisions

The Ruby Runtime strives to provide a simple, idiomatic Ruby API for extending Jenkins. You should use this method for extensions wherever you can

However, the native Jenkins API is vast, and the idiomatic API only covers a small percentage of it. This situation will surely improve, but in the meantime, you may find yourself wanting to extend Jenkins with an set of extension points that just aren't available to Ruby yet.

In these cases, you can extend the Jenkins Extension point directly and the Ruby runtime will help you do it as easily as possible.

Two types of extensions

Extensions in Jenkins are divided into two categories: singletons and describables. Singletons are objects instantiated on system startup and generally scoped to the life of the server. Describables on the other hand are many, created in response to some configuration event and are scoped to some other object in the system.

We'll go through implementing these two types of extensions by directly scripting the Java API starting with the singleton.

RunListener singleton

We'll be working with the Jenkins RunListener interface. This is a wonderful extension point that allows you to receive callbacks at each point during the actual running of a build. There's actually already a nice ruby API for it, but we won't let that stop our demonstrations

We'll start here with the most basic RunListener

class MyListener < Java.hudson.model.listeners.RunListener
  def initialize()
    super(Java.hudson.model.Run.java_class)
  end
end

There's a couple key takeaways here. First, notice that we use JRuby integration to extend the class hudson.model.listeners.RunListener directly. Second, and this is a gotcha anytime you extend a Java class: you must invoke one of the Java super constructors if it does not have a default constructor. I can't tell you how many times I've been bitten by this. Your constructor must also take no arguments

In our case, the RunListener class filters which jobs it will provide callbacks for by class. By providing a more specific class to the constructor, you limit the scope of jobs you'll receive to subclasses of that class. For our purposes, we cast a pretty wide net by selecting all builds via the AbstractBuild Java class.

Pro Tip: when you're implementing a native Java API, it really helps to have the javadoc open in one window so that you can view the documentation and crib from the source

Now that we've got our class defined, let's implement some methods! We'll add callbacks for when a build is started and when it's completed.

class MyListener < Java.hudson.model.listeners.RunListener
  def initialize()
    super(Java.hudson.model.AbstractBuild.java_class)
  end
  def onStarted(run, listener)
    listener.getLogger().println("onStarted(#{run})")
  end
  def onCompleted(run, listener)
    listener.getLogger().println("onCompleted(#{run})")
  end
end

And finally, we need to tell Jenkins that this is a singleton that it can pick up and use an an extension point. We do this by including the Jenkins::Model::SingletonNative module.

class MyListener < Java.hudson.model.listeners.RunListener
  include Jenkins::Model::SingletonNative
  def initialize()
    super(Java.hudson.model.AbstractBuild.java_class)
  end
  def onStarted(run, listener)
    listener.getLogger().println("onStarted(#{run})")
  end
  def onCompleted(run, listener)
    listener.getLogger().println("onCompleted(#{run})")
  end
end

That's it! You've got your native RunListener

Builder describable

Working with Describable extensions is just as easy. Let's make a native HelloWorld builder.

Again, there is a very pleasant Ruby API for Builder, you should always try to see if there is an idiomatic API before you go off using native apis

We'll make it print out "Hello World!" as the implementation of its build step.

class HelloWorldBuilder < Java.hudson.tasks.Builder
  include Jenkins::Model::DescribableNative

  def perform(build, launcher, listener)
    listener.getLogger().println("Hello World!")
    return true
  end
end

Like the Singleton, we include a module to tell Jenkins that this builder is an extension, but this time we include Jenkins::Model::DescribableNative to indicate that it is describable.

Like other describable extension, its views will be stored in views/hello_world_builder.

You can also give it a display name to show whenever it appears in the UI. Just remember that this is a class method, not an instance method e.g.

class HelloWorldBuilder < Java.hudson.tasks.Builder
  include Jenkins::Model::DescribableNative

  def self.getDisplayName()
    "The Fabulous Hello World Builder"
  end

  def perform(build, launcher, listener)
    listener.getLogger().println("Hello World!")
    return true
  end
end