Package ruby

Source Code of ruby.RubyPlugin

package ruby;

import hudson.ExtensionComponent;
import hudson.Util;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyModule;
import org.jruby.embed.ScriptingContainer;
import org.jruby.javasupport.Java;
import org.jruby.rack.DefaultRackApplication;
import org.jruby.rack.servlet.ServletRackConfig;
import org.jruby.rack.servlet.ServletRackContext;
import org.jruby.rack.servlet.ServletRackEnvironment;
import org.jruby.rack.servlet.ServletRackResponseEnvironment;
import org.jruby.runtime.builtin.IRubyObject;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.jelly.jruby.RubyKlassNavigator;
import org.kohsuke.stapler.lang.Klass;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;


/**
* The primary Java interface to a plugin which is implemented in Ruby.
* One instance is created per each Ruby plugin.
*
* <p>
* When this plugin initializes, it will instantiate a Jenkins::Plugin
* object which acts as the gateway for Ruby to interact with the java
* side.
* <p>
* When the RubyPlugin is loaded, it will discover, load and provide
* a mechanism for extensions written in Ruby that it contains to register
* themselves.
* <p>
* These Extensions are presented to Jenkins via the {@link RubyExtensionFinder}
* <p>
* Each plugin has its own JRuby environment
*/
@SuppressWarnings({"UnusedDeclaration"})
public class RubyPlugin extends PluginImpl {


  /**
   * A reference to the loadpath where this ruby runtime will search whenever
   * a library is `require`d
   */
  private RubyArray loadPath;

  /**
   * The unique JRuby environment used by this plugin and all the objects
   * and classes that it contains.
   */
  ScriptingContainer ruby;

    private /*almost final*/ RubyKlassNavigator navigator;

    /**
     * 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
     */
  private Object plugin;

  private ArrayList<ExtensionComponent> extensions;

    /**
     * Directory to load ruby lib/*.rb from.
     */
    private File libPath;

    /**
     * Directory to load ruby model definitions.
     */
    private File modelsPath;

    private ServletRackContext rackContext;

  /**
   * 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 Object callMethod(Object object, String methodName, Object... args) {
    return ruby.callMethod(object, methodName, args);
  }

  /**
   * Registers an extension with this Ruby plugin so that it will be found later on
   * <p/>
   * 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 Collection<ExtensionComponent> getExtensions() {
    return extensions;
  }

  public RubyPlugin() {
    this.extensions = new ArrayList<ExtensionComponent>();
  }

    /**
     * Loads the models.rb
     */
    public String loadBootScript() throws IOException {
        File rb = new File(getLibPath(),"models.rb");
        if (rb.exists()) {
            return FileUtils.readFileToString(rb);
        } else {
            return "";
        }
    }

  /**
   * 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 {
    //This seems to be instantiatiating RubyPlugin for the abstract instance
    //I thought it had been working to not do so at one point...
    if (this.getWrapper().getShortName().equals("ruby-runtime"))    return;

        this.extensions = new ArrayList<ExtensionComponent>();
        ruby = new ScriptingContainerHolder().ruby;
        navigator = new RubyKlassNavigator(ruby.getProvider().getRuntime(),getWrapper().classLoader);

        initRubyLoadPaths();
        initRubyNativePlugin();

        rackContext = new ServletRackContext(new ServletRackConfig(Jenkins.getInstance().servletContext));
  }

    /**
     * Gets the plugin that owns the container.
     */
    public static RubyPlugin from(Ruby r) {
        IRubyObject v = r.evalScriptlet("Jenkins::Plugin.instance.peer");
        if (v==null)        return null;
        return (RubyPlugin) v.toJava(RubyPlugin.class);
    }
   
    public Klass<RubyModule> klassFor(RubyModule module) {
        return module!=null ? new Klass<RubyModule>(module,navigator) : null;
    }

  private void initRubyNativePlugin() {
    require("jenkins/plugin/runtime");
    Object pluginClass = eval("Jenkins::Plugin");
    this.plugin = callMethod(pluginClass, "initialize", this);
    callMethod(plugin, "start");
  }

  private void initRubyLoadPaths() throws Exception {
    this.loadPath = (RubyArray)eval("$:");
        this.libPath = getPathFromManifest("Lib-Path","lib");
        this.modelsPath = getPathFromManifest("Models-Path","models");

    //the Load-Path entry will be explicitly set when we are running the test server
    //during plugin development. This allows you to just load the right gems off of the

    String loadPaths = getWrapper().getManifest().getMainAttributes().getValue("Load-Path");
    if (loadPaths != null) {
      for (String path: loadPaths.split(":")) {
        addLoadPath(path);
      }
    } else {
      //If we aren't explicitly passing the Load-Path, then this must be in production mode.
      //we will have a full standalone gem bundle, that generates the loadpaths for us
      File gemsHome = getPathFromManifest("Bundle-Path", "vendor/gems");
      if (!gemsHome.exists()) {
        throw new Exception("unable to locate gem bundle for " + getWrapper().getShortName() + " at " + gemsHome.getAbsolutePath());
      }
      addLoadPath(gemsHome.getAbsolutePath());
      require("bundler/setup");
    }
    addLoadPath(this.libPath.getAbsolutePath());

        // make it easier to load arbitrary scripts from the file system, especially during the development
        for (String path : Util.fixNull(System.getProperty("jenkins.ruby.paths")).split(",")) {
            if (path.length()==0)   continue;   // "".split(",")=>[""]
          addLoadPath(path);
        }
  }

  private Object eval(String script) {
    return this.ruby.runScriptlet(script);
  }

  private void require(String path) {
    eval("require '" + path + "'");
  }

  private void addLoadPath(String path) {
    callMethod(this.loadPath, "unshift", path);
  }

  private File getPathFromManifest(String attributeName, String defaultValue) {
        String v = getWrapper().getManifest().getMainAttributes().getValue(attributeName);
        if (v ==null)   v = defaultValue;
        return resolve(getScriptDir(), v);
    }

    private static File resolve(File base, String relative) {
        File rel = new File(relative);
        if(rel.isAbsolute())
            return rel;
        else
            return new File(base,relative);
    }

  /**
   * 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 {
    if (this.ruby != null) {
      callMethod(plugin, "stop");
    }
  }

  public String getResourceURI(String relativePathFormat, Object... args) {
    return getClass().getResource(String.format(relativePathFormat, args)).getPath();
  }

    /**
     * Returns a directory that stores all the Ruby scripts.
     */
    public File getScriptDir() {
        URL url = getWrapper().baseResourceURL;
        // we assume url to be file:// path because we later need to be able to enumerate them
        // to lift this limitation, we need build-time processing to enumerate all the rb files.
        if (!url.getProtocol().equals("file"))
            throw new IllegalStateException("Unexpected base resource URL: "+url);

        return new File(new File(url.getPath()),"WEB-INF/classes");
    }

    public File getLibPath() {
        return libPath;
    }

    public File getModelsPath() {
        return modelsPath;
    }

  public ScriptingContainer getScriptingContainer() {
    return ruby;
  }

  public Object getNativeRubyPlugin() {
    return this.plugin;
  }

    /**
     * Let a Rack application handle the current request.
     *
     * @param servletHandler
     *      Instance of Rack::Handler::Servlet which wraps the actual rack application and provides
     *      the ruby part of the rack implementation.
     */
    public void rack(IRubyObject servletHandler) {
        final StaplerRequest req = Stapler.getCurrentRequest();
        // we don't want the Rack app to consider the portion of the URL that was already consumed
        // to reach to the Rack app, so for PATH_INFO we use getRestOfPath(), not getPathInfo()
        ServletRackEnvironment env = new ServletRackEnvironment(req, rackContext) {
            @Override
            public String getPathInfo() {
                return req.getRestOfPath();
            }
        };
        DefaultRackApplication dra = new DefaultRackApplication();
        dra.setApplication(servletHandler);
        dra.call(env)
                .respond(new ServletRackResponseEnvironment(Stapler.getCurrentResponse()));
    }
}
TOP

Related Classes of ruby.RubyPlugin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.