Permalink
Browse files

add lots of documentation, add jenkins-plugins ruby support lib as an…

… external.
  • Loading branch information...
1 parent 4bec578 commit a9a359e2ad6255716c0f115e89a76cf688f8dd2b @cowboyd committed Jun 3, 2011
View
@@ -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
@@ -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);
@@ -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 {
@@ -23,7 +35,6 @@
hits.add(c);
}
}
- System.out.printf("RubyExtensionFinder.find(%s) -> %d extensions\n",type, hits.size());
return hits;
}
}
@@ -1,6 +1,5 @@
package ruby;
-import com.thoughtworks.xstream.XStream;
import hudson.Extension;
import hudson.ExtensionComponent;
import hudson.Plugin;
@@ -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);
@@ -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 {
@@ -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());
}
@@ -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
@@ -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);

0 comments on commit a9a359e

Please sign in to comment.