001package org.kohsuke.stapler.jelly.jruby;
002
003import org.jruby.Ruby;
004import org.jruby.RubyModule;
005import org.jruby.RubyObject;
006import org.jruby.internal.runtime.methods.DynamicMethod;
007import org.jruby.rack.DefaultRackApplication;
008import org.jruby.rack.servlet.*;
009import org.jruby.runtime.builtin.IRubyObject;
010import org.kohsuke.stapler.Dispatcher;
011import org.kohsuke.stapler.RequestImpl;
012import org.kohsuke.stapler.ResponseImpl;
013import org.kohsuke.stapler.Stapler;
014
015import javax.servlet.ServletException;
016import java.io.IOException;
017import java.lang.reflect.InvocationTargetException;
018
019/**
020 * {@link Dispatcher} that looks for the Rack-compliant call method.
021 *
022 * @author Kohsuke Kawaguchi
023 */
024public class RackDispatcher extends Dispatcher {
025    @Override
026    public boolean dispatch(final RequestImpl req, ResponseImpl rsp, Object node) throws IOException, ServletException, IllegalAccessException, InvocationTargetException {
027        RubyObject x = (RubyObject) node;
028        Ruby runtime = x.getRuntime();
029
030        DynamicMethod m = x.getMetaClass().searchMethod("call");
031        if (m==null) // does this instance respond to the 'call' method?
032            return false;
033
034        // TODO: does the context need to live longer?
035        ServletRackContext rackContext = new DefaultServletRackContext(new ServletRackConfig(req.getServletContext()));
036
037        // we don't want the Rack app to consider the portion of the URL that was already consumed
038        // to reach to the Rack app, so for PATH_INFO we use getRestOfPath(), not getPathInfo()
039        ServletRackEnvironment env = new ServletRackEnvironment(req, rsp, rackContext) {
040            @Override
041            public String getPathInfo() {
042                return req.getRestOfPath();
043            }
044        };
045        // servletHandler = Rack::Handler::Servlet.new(node)
046        runtime.getLoadService().require("rack/handler/servlet");
047        IRubyObject servletHandler = ((RubyModule)runtime.getModule("Rack").getConstantAt("Handler")).getClass("Servlet").callMethod("new", x);
048
049        DefaultRackApplication dra = new DefaultRackApplication();
050        dra.setApplication(servletHandler);
051        dra.call(env)
052                .respond(new ServletRackResponseEnvironment(Stapler.getCurrentResponse()));
053
054        return true;
055    }
056
057    public String toString() {
058        return "call(env) to delegate to Rack-compatible Ruby objects";
059    }
060}