Skip to content

Commit

Permalink
add lots of documentation, add jenkins-plugins ruby support lib as an…
Browse files Browse the repository at this point in the history
… external.
  • Loading branch information
cowboyd committed Jun 3, 2011
1 parent 4bec578 commit a9a359e
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 20 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "src/main/resources/ruby/jenkins-plugins"]
path = src/main/resources/ruby/jenkins-plugins
url = https://github.com/cowboyd/jenkins-plugins.rb.git
8 changes: 8 additions & 0 deletions src/main/java/ruby/RubyDoDynamic.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

/**
* This interface is meant to be included by JRuby proxies so that they
* can respond directly to stapler requests.
*
* If I understand correctly, stapler will see if the <code>doDynamic</code>
* method exists and if so, dispatch it via that method.
*/

public interface RubyDoDynamic {

void doDynamic(StaplerRequest request, StaplerResponse response);
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/ruby/RubyExtensionFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@
import hudson.Extension;
import hudson.ExtensionComponent;
import hudson.ExtensionFinder;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.model.Hudson;

import java.util.ArrayList;
import java.util.Collection;


/**
* Presents Ruby extensions to Jenkins.
*
* Whenever a ruby plugin loads, it scans its codebase and finds all of the objects
* which implement Jenkins extensions points (there can be any number of these per plugin)
*
* Sometime later, whenever Jenkins is asking about a particular extension type, like a
* Publisher on BuildWrapper, it will query this ExtensionFinder among others. This finder then
* delegates to the ruby plugin to see if it has any extensions of the requested type.
*
* @see hudson.ExtensionPoint
*/

@SuppressWarnings({"UnusedDeclaration"})
@Extension
public class RubyExtensionFinder extends ExtensionFinder {
Expand All @@ -23,7 +35,6 @@ public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson)
hits.add(c);
}
}
System.out.printf("RubyExtensionFinder.find(%s) -> %d extensions\n",type, hits.size());
return hits;
}
}
115 changes: 98 additions & 17 deletions src/main/java/ruby/RubyPlugin.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ruby;

import com.thoughtworks.xstream.XStream;
import hudson.Extension;
import hudson.ExtensionComponent;
import hudson.Plugin;
Expand All @@ -9,59 +8,119 @@
import hudson.model.Hudson;
import hudson.model.Items;
import hudson.util.XStream2;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.jenkinsci.jruby.JRubyMapper;
import org.jenkinsci.jruby.JRubyXStream;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.ScriptingContainer;
import org.jruby.javasupport.proxy.InternalJavaProxy;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.jelly.JellyClassTearOff;

import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;

@Extension

/**
* The primary Java interface to a plugin which is implemented in Ruby
*
* When this plugin initializes, it will instantiate a Jenkins::Plugin
* object which acts as the gateway for Ruby to interact with the java
* side.
*
* When the RubyPlugin is loaded, it will discover, load and provide
* a mechanism for extensions written in Ruby that it contains to register
* themselves.
*
* These Extensions are presented to Jenkins via the {@link RubyExtensionFinder}
*
* Each plugin has its own JRuby environment
*/
@SuppressWarnings({"UnusedDeclaration"})
@Extension
public class RubyPlugin extends Plugin implements Describable<RubyPlugin> {
/**
* The unique JRuby environment used by this plugin and all the objects
* and classes that it contains.
*/
private ScriptingContainer ruby;


private Object plugin;
private ArrayList<ExtensionComponent> extensions;

public static RubyPlugin get() {
return Hudson.getInstance().getPlugin(RubyPlugin.class);
}

/**
* Kinda acts like the "agent" of this ruby plugin in the Ruby world.
* This is the object that the internals of the ruby side talk to when
* then want to talk back to Java.
* @return an instance of Jenkins::Plugin
*/
public static Object getRubyController() {
return get().plugin;
}

/**
* invokes a Ruby method on the specified object in the context of this plugin's
* {@link ScriptingContainer}
*
* @param object <b>JRuby</b> object to use as invocant
* @param methodName the method to end
* @param args arguments to the method
* @return the return value of the method call.
*/
public static Object callMethod(Object object, String methodName, Object... args) {
return RubyPlugin.get().ruby.callMethod(object, methodName, args);
}

/**
* Registers an extenion with this Ruby plugin so that it will be found later on
*
* This method is generally called from inside Ruby, as objects that implement
* extension points register themselves.
* @param extension
*/
public void addExtension(Object extension) {
extensions.add(new ExtensionComponent(extension));
}

/**
* @return the list of extensions registered with this Plugin. this is used by
* the {@link RubyExtensionFinder} to present extension points to Jenkins
*/
public static Collection<ExtensionComponent> getExtensions() {
return get().extensions;
}

/**
* Reads a resource relative to this plugin's Java class using a formatted string
* @param resource the string template specifying the resource
* @param args format arguments
* @return the content of the resource
*/
public static String readf(String resource, Object... args) {
return RubyPlugin.get().read(String.format(resource, args));
}

/**
* Initializes this plugin by setting up the JRuby scripting container
* and then loading up the ruby side of the plugin by creating an
* instance of the Ruby class Jenkins::Plugin which will serve as
* its agent in the Ruby world.
*
* We also register xstream mappers for JRuby objects so that they
* can be persisted along with other objects in Jenkins.
*/
public RubyPlugin() {
this.ruby = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
this.ruby = new ScriptingContainer(LocalContextScope.THREADSAFE);
this.ruby.setClassLoader(this.getClass().getClassLoader());
this.ruby.getLoadPaths().add(0, this.getClass().getResource("support").getPath());
this.ruby.getLoadPaths().add(this.getClass().getResource("jenkins-plugins/lib").getPath());
this.ruby.getLoadPaths().add(this.getClass().getResource(".").getPath());
this.extensions = new ArrayList<ExtensionComponent>();
this.ruby.runScriptlet("require 'hudson/plugin/controller'");
Object pluginClass = this.ruby.runScriptlet("Hudson::Plugin::Controller");
this.ruby.runScriptlet("require 'jenkins/plugin'");
Object pluginClass = this.ruby.runScriptlet("Jenkins::Plugin");
this.plugin = this.ruby.callMethod(pluginClass, "new", this);

register((XStream2)Hudson.XSTREAM, ruby);
Expand All @@ -75,6 +134,11 @@ private void register(XStream2 xs, ScriptingContainer ruby) {
}
}

/**
* Read a resource relative to this plugin clas
* @param resource the name of the resource to be read
* @return the content of the resource
*/
public String read(String resource) {
InputStream stream = this.getClass().getResourceAsStream(resource);
try {
Expand All @@ -91,17 +155,34 @@ public String read(String resource) {
}
}

/**
* Jenkins will call this method whenever the plugin is initialized
* The plugin will in turn delegate to its instance of Jenkins::Plugin
* which can take action on the Ruby side.
* @throws Exception
*/
@Override
public void start() throws Exception {
//WebApp.getCurrent().getMetaClass(Object.class).dispatchers.add(0, new RubyDispatcher());
this.ruby.callMethod(plugin, "start");
}

/**
* Jenkins will call this method whenever the plugin is shut down
* The plugin will in turn delegate to its instance of Jenkins::Plugin
* which can take action on the Ruby side
* @throws Exception
*/
@Override
public void stop() throws Exception {
this.ruby.callMethod(plugin, "stop");
}


/**
* This is mandatory for Jenkins to find this plugin, although I'm not
* exactly sure why.
* @return
*/
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)Hudson.getInstance().getDescriptorOrDie(getClass());
}
Expand All @@ -110,10 +191,10 @@ public static String getResourceURI(String relativePathFormat, Object... args) {
return get().getClass().getResource(String.format(relativePathFormat, args)).getPath();
}

public static Collection<ExtensionComponent> getExtensions() {
return get().extensions;
}

/**
* Again, this is mandatory for Jenkins to find this plugin, although I'm not
* exactly sure why.
*/
@Extension
public static final class DescriptorImpl extends Descriptor<RubyPlugin> {
@Override
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/ruby/SimpleGet.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package ruby;


/**
* When stapler is querying a Java object to see which properties it has
* it normally uses reflection to see if there is a field or getter with the
* corresponding name which it can use.
*
* You obviously can't do this on a JRuby object, so instead Stapler and Jenkins
* will look and see if it has a get(String) method and if so, use that for
* property lookup.
*
* JRuby proxies include this interface to support this.
*/
public interface SimpleGet {

Object get(String name);
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/ruby/jenkins-plugins
Submodule jenkins-plugins added at e9ccc8

0 comments on commit a9a359e

Please sign in to comment.