Package org.jruby.rack

Source Code of org.jruby.rack.DefaultRackApplicationFactory$ApplicationObjectFactory

/*
* Copyright (c) 2010-2012 Engine Yard, Inc.
* Copyright (c) 2007-2009 Sun Microsystems, Inc.
* This source code is available under the MIT license.
* See the file LICENSE.txt for details.
*/

package org.jruby.rack;

import java.io.IOException;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.JavaUtil;
import org.jruby.rack.servlet.ServletRackContext;
import org.jruby.rack.servlet.RewindableInputStream;
import org.jruby.rack.util.IOHelpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import static org.jruby.rack.DefaultRackConfig.isIgnoreRUBYOPT;

/**
* Default application factory creates a new application instance on each
* {@link #getApplication()} invocation. It does not manage applications it
* creates (except for the error application that is assumed to be shared).
*
* @see SharedRackApplicationFactory
* @see PoolingRackApplicationFactory
*
* @author nicksieger
*/
public class DefaultRackApplicationFactory implements RackApplicationFactory {
   
    private String rackupScript, rackupLocation;
    private ServletRackContext rackContext;
    private RubyInstanceConfig runtimeConfig;
    private RackApplication errorApplication;

    /**
     * Convenience helper for unwrapping a {@link RackApplicationFactoryDecorator}.
     * @param factory the (likely decorated) factory
     * @return unwrapped "real" factory (might be the same as given)
     */
    public static RackApplicationFactory getRealFactory(final RackApplicationFactory factory) {
        if ( factory instanceof RackApplicationFactory.Decorator ) {
            return getRealFactory( ((Decorator) factory).getDelegate() );
        }
        return factory;
    }
   
    public RackContext getRackContext() {
        return rackContext;
    }
   
    public String getRackupScript() {
        return rackupScript;
    }

    public void setRackupScript(String rackupScript) {
        this.rackupScript = rackupScript;
        this.rackupLocation = null;
    }
   
    /**
     * Initialize this factory using the given context.
     * <br/>
     * NOTE: exception handling is left to the outer factory.
     * @param rackContext
     */
    public void init(final RackContext rackContext) {
        // NOTE: this factory is not supposed to be directly exposed
        // thus does not wrap exceptions into RackExceptions here ...
        // same applies for #newApplication() and #getApplication()
        this.rackContext = (ServletRackContext) rackContext;
        if ( getRackupScript() == null ) resolveRackupScript();
        this.runtimeConfig = createRuntimeConfig();
        rackContext.log(RackLogger.INFO, runtimeConfig.getVersionString());
        configureDefaults();
    }

    /**
     * Creates a new application instance (without initializing it).
     * <br/>
     * NOTE: exception handling is left to the outer factory.
     * @return new application instance
     */
    public RackApplication newApplication() {
        return createApplication(new ApplicationObjectFactory() {
            public IRubyObject create(Ruby runtime) {
                return createApplicationObject(runtime);
            }
        });
    }

    /**
     * Creates a new application and initializes it.
     * <br/>
     * NOTE: exception handling is left to the outer factory.
     * @return new, initialized application
     */
    public RackApplication getApplication() {
        final RackApplication app = newApplication();
        app.init();
        return app;
    }

    /**
     * Destroys the application (assumably) created by this factory.
     * <br/>
     * NOTE: exception handling is left to the outer factory.
     * @param app the application to "release"
     */
    public void finishedWithApplication(final RackApplication app) {
        if ( app != null ) app.destroy();
    }

    /**
     * @return the (default) error application
     */
    public RackApplication getErrorApplication() {
        if (errorApplication == null) {
            synchronized(this) {
                if (errorApplication == null) {
                    errorApplication = newErrorApplication();
                }
            }
        }
        return errorApplication;
    }

    /**
     * Set the (default) error application to be used.
     * @param errorApplication
     */
    public synchronized void setErrorApplication(RackApplication errorApplication) {
        this.errorApplication = errorApplication;
    }
   
    public void destroy() {
        if (errorApplication != null) {
            synchronized(this) {
                if (errorApplication != null) {
                    errorApplication.destroy();
                    errorApplication = null;
                }
            }
        }
    }

    public IRubyObject createApplicationObject(final Ruby runtime) {
        if (rackupScript == null) {
            rackContext.log(RackLogger.WARN, "no rackup script found - starting empty Rack application!");
            rackupScript = "";
        }
        checkAndSetRackVersion(runtime);
        runtime.evalScriptlet("load 'jruby/rack/boot/rack.rb'");
        return createRackServletWrapper(runtime, rackupScript, rackupLocation);
    }

    public IRubyObject createErrorApplicationObject(final Ruby runtime) {
        String errorApp = rackContext.getConfig().getProperty("jruby.rack.error.app");
        String errorAppPath = "<web.xml>";
        if (errorApp == null) {
            errorApp = rackContext.getConfig().getProperty("jruby.rack.error.app.path");
            if (errorApp != null) {
                errorAppPath = rackContext.getRealPath(errorApp);
                try {
                    errorApp = IOHelpers.inputStreamToString(rackContext.getResourceAsStream(errorApp));
                }
                catch (IOException e) {
                    rackContext.log(RackLogger.WARN,
                        "failed to read jruby.rack.error.app.path = '" + errorApp + "' " +
                        "will use default error application", e);
                    errorApp = errorAppPath = null;
                }
            }
           
        }
        if (errorApp == null) {
            errorApp = "require 'jruby/rack/error_app' \n" +
            "use Rack::ShowStatus \n" +
            "run JRuby::Rack::ErrorApp.new";
        }
        runtime.evalScriptlet("load 'jruby/rack/boot/rack.rb'");
        return createRackServletWrapper(runtime, errorApp, errorAppPath);
    }

    public RackApplication newErrorApplication() {
        Boolean error = rackContext.getConfig().getBooleanProperty("jruby.rack.error");
        if ( error != null && ! error.booleanValue() ) { // jruby.rack.error = false
            return new DefaultErrorApplication(rackContext);
        }
        try {
            RackApplication app = createErrorApplication(
                new ApplicationObjectFactory() {
                    public IRubyObject create(Ruby runtime) {
                        return createErrorApplicationObject(runtime);
                    }
                }
            );
            app.init();
            return app;
        }
        catch (Exception e) {
            rackContext.log(RackLogger.WARN, "error application could not be initialized", e);
            return new DefaultErrorApplication(rackContext); // backwards compatibility
        }
    }

    /**
     * @see #createRackServletWrapper(Ruby, String, String)
     * @param runtime
     * @param rackup
     * @return (Ruby) built Rack Servlet handler
     */
    protected IRubyObject createRackServletWrapper(Ruby runtime, String rackup) {
        return createRackServletWrapper(runtime, rackup, null);
    }

    /**
     * Creates the handler to bridge the Servlet and Rack worlds.
     * @param runtime
     * @param rackup
     * @param filename
     * @return (Ruby) built Rack Servlet handler
     */
    protected IRubyObject createRackServletWrapper(Ruby runtime, String rackup, String filename) {
        return runtime.executeScript(
            "Rack::Handler::Servlet.new( " +
                "Rack::Builder.new { (" + rackup + "\n) }.to_app " +
            ")", filename
        );
    }
   
    static interface ApplicationObjectFactory {
        IRubyObject create(Ruby runtime) ;
    }

    public RubyInstanceConfig createRuntimeConfig() {
        setupJRubyManagement();
        return initRuntimeConfig(new RubyInstanceConfig());
    }
   
    protected RubyInstanceConfig initRuntimeConfig(final RubyInstanceConfig config) {
        final RackConfig rackConfig = rackContext.getConfig();
       
        config.setLoader(Thread.currentThread().getContextClassLoader());
       
        // Don't affect the container and sibling web apps when ENV changes are
        // made inside the Ruby app ...
        // There are quite a such things made in a typical Bundler based app.
        config.setUpdateNativeENVEnabled(false);
       
        final Map<String, String> newEnv = rackConfig.getRuntimeEnvironment();
        if ( newEnv != null ) {
            if ( ! newEnv.containsKey("PATH") ) {
                // bundler 1.1.x assumes ENV['PATH'] is a string
                // `ENV['PATH'].split(File::PATH_SEPARATOR)` ...
                newEnv.put("PATH", ""); // ENV['PATH'] = ''
            }
            // bundle exec sets RUBYOPT="-I[...]/gems/bundler/lib -rbundler/setup"
            if ( isIgnoreRUBYOPT(rackConfig) ) {
                if ( newEnv.containsKey("RUBYOPT") ) newEnv.put("RUBYOPT", "");
            }
            else {
                // allow to work (backwards) "compatibly" with previous `ENV.clear`
                // RUBYOPT was processed since it happens on config.processArguments
                @SuppressWarnings("unchecked")
                final Map<String, String> env = config.getEnvironment();
                if ( env != null && env.containsKey("RUBYOPT") ) {
                    newEnv.put( "RUBYOPT", env.get("RUBYOPT") );
                }
            }
            config.setEnvironment(newEnv);
        }
       
        // Process arguments, namely any that might be in RUBYOPT
        config.processArguments(rackConfig.getRuntimeArguments());
       
        if ( rackConfig.getCompatVersion() != null ) {
            config.setCompatVersion(rackConfig.getCompatVersion());
        }

        try { // try to set jruby home to jar file path
            URL resource = RubyInstanceConfig.class.getResource("/META-INF/jruby.home");
            if (resource != null && resource.getProtocol().equals("jar")) {
                String home;
                try { // http://weblogs.java.net/blog/2007/04/25/how-convert-javaneturl-javaiofile
                    home = resource.toURI().getSchemeSpecificPart();
                }
                catch (URISyntaxException e) {
                    home = resource.getPath();
                }
                // Trim trailing slash. It confuses OSGi containers...
                if (home.endsWith("/")) {
                    home = home.substring(0, home.length() - 1);
                }
                config.setJRubyHome(home);
            }
        }
        catch (Exception e) {
            rackContext.log(RackLogger.DEBUG, "won't set-up jruby.home from jar", e);
        }

        return config;
    }
   
    public Ruby newRuntime() throws RaiseException {
        final Ruby runtime = Ruby.newInstance(runtimeConfig);
        initRuntime(runtime);
        return runtime;
    }
   
    /**
     * Initializes the runtime (exports the context, boots the Rack handler).
     *
     * NOTE: (package) visible due specs
     *
     * @param runtime
     */
    void initRuntime(final Ruby runtime) {
        // set $servlet_context :
        runtime.getGlobalVariables().set(
            "$servlet_context", JavaUtil.convertJavaToRuby(runtime, rackContext)
        );
        // load our (servlet) Rack handler :
        runtime.evalScriptlet("require 'rack/handler/servlet'");
       
        // NOTE: this is experimental stuff and might change in the future :
        String env = rackContext.getConfig().getProperty("jruby.rack.handler.env");
        // currently supported "env" values are 'default' and 'servlet'
        if ( env != null ) {
            runtime.evalScriptlet("Rack::Handler::Servlet.env = '" + env + "'");
        }
        String response = rackContext.getConfig().getProperty("jruby.rack.handler.response");
        if ( response == null ) {
            response = rackContext.getConfig().getProperty("jruby.rack.response");
        }
        if ( response != null ) { // JRuby::Rack::JettyResponse -> 'jruby/rack/jetty_response'
            runtime.evalScriptlet("Rack::Handler::Servlet.response = '" + response + "'");
        }
       
        // configure (Ruby) bits and pieces :
        String dechunk = rackContext.getConfig().getProperty("jruby.rack.response.dechunk");
        Boolean dechunkFlag = (Boolean) DefaultRackConfig.toStrictBoolean(dechunk, null);
        if ( dechunkFlag != null ) {
            runtime.evalScriptlet("JRuby::Rack::Response.dechunk = " + dechunkFlag + "");
        }
        else { // dechunk null (default) or not a true/false value ... we're patch :
            runtime.evalScriptlet("JRuby::Rack::Booter.on_boot { require 'jruby/rack/chunked' }");
            // `require 'jruby/rack/chunked'` that happens after Rack is loaded
        }
    }

    /**
     * Checks and sets the required Rack version (if specified as a magic comment).
     *
     * e.g. # rack.version: ~>1.3.6
     *
     * NOTE: (package) visible due specs
     *
     * @param runtime
     * @return the rack version requirement
     */
    String checkAndSetRackVersion(final Ruby runtime) {
        String rackVersion = null;
        try {
            rackVersion = IOHelpers.rubyMagicCommentValue(rackupScript, "rack.version:");
        }
        catch (Exception e) {
            rackContext.log(RackLogger.DEBUG, "could not read 'rack.version' magic comment from rackup", e);
        }
       
        if ( rackVersion == null ) {
            // NOTE: try matching a `require 'bundler/setup'` line ... maybe not ?!
        }
        if ( rackVersion != null ) {
            runtime.evalScriptlet("require 'rubygems'");
           
            if ( rackVersion.equalsIgnoreCase("bundler") ) {
                runtime.evalScriptlet("require 'bundler/setup'");
            }
            else {
                rackContext.log(RackLogger.DEBUG, "detected 'rack.version' magic comment, " +
                        "will use `gem 'rack', '"+ rackVersion +"'`");
                runtime.evalScriptlet("gem 'rack', '"+ rackVersion +"' if defined? gem");
            }
        }
        return rackVersion;
    }
   
    private RackApplication createApplication(final ApplicationObjectFactory appFactory) {
        return new RackApplicationImpl(appFactory);
    }
   
    /**
     * The application implementation this factory is producing.
     */
    private class RackApplicationImpl extends DefaultRackApplication {
       
        private final Ruby runtime;
        final ApplicationObjectFactory appFactory;
       
        RackApplicationImpl(ApplicationObjectFactory appFactory) {
            this.runtime = newRuntime();
            this.appFactory = appFactory;
        }
       
        @Override
        public void init() {
            try {
                setApplication(appFactory.create(runtime));
            }
            catch (RaiseException e) {
                captureMessage(e);
                throw e;
            }
        }
       
        @Override
        public void destroy() {
            runtime.tearDown(false);
        }
       
    }
   
    private RackApplication createErrorApplication(final ApplicationObjectFactory appFactory) {
        final Ruby runtime = newRuntime();
        return new DefaultErrorApplication() {
            @Override
            public void init() {
                setApplication(appFactory.create(runtime));
            }
            @Override
            public void destroy() {
                runtime.tearDown(false);
            }
        };
    }
   
    private void captureMessage(final RaiseException re) {
        try {
            IRubyObject rubyException = re.getException();
            ThreadContext context = rubyException.getRuntime().getCurrentContext();
            // JRuby-Rack internals (@see jruby/rack/capture.rb) :
            rubyException.callMethod(context, "capture");
            rubyException.callMethod(context, "store");
        }
        catch (Exception e) {
            rackContext.log(RackLogger.INFO, "failed to capture exception message", e);
            // won't be able to capture anything
        }
    }

    private String findConfigRuPathInSubDirectories(final String path, int level) {
        @SuppressWarnings("unchecked")
        final Set<String> entries = rackContext.getResourcePaths(path);
        if (entries != null) {
            String config_ru = path + "config.ru";
            if ( entries.contains(config_ru) ) {
                return config_ru;
            }

            if (level > 0) {
                level--;
                for ( Iterator<String> i = entries.iterator(); i.hasNext(); ) {
                    String subpath = i.next();
                    if (subpath.endsWith("/")) {
                        subpath = findConfigRuPathInSubDirectories(subpath, level);
                        if (subpath != null) {
                            return subpath;
                        }
                    }
                }
            }
        }
        return null;
    }

    private String resolveRackupScript() throws RackInitializationException {
        rackupLocation = "<web.xml>";

        String rackup = rackContext.getConfig().getRackup();
        if (rackup == null) {
            rackup = rackContext.getConfig().getRackupPath();

            if (rackup == null) {
                rackup = findConfigRuPathInSubDirectories("/WEB-INF/", 1);
            }
            if (rackup == null) { // google-appengine gem prefers it at /config.ru
                // appengine misses "/" resources. Search for it directly.
                String rackupPath = rackContext.getRealPath("/config.ru");
                if (rackupPath != null && new File(rackupPath).exists()) {
                    rackup = "/config.ru";
                }
            }

            if (rackup != null) {
                rackupLocation = rackContext.getRealPath(rackup);
                try {
                    rackup = IOHelpers.inputStreamToString(rackContext.getResourceAsStream(rackup));
                }
                catch (IOException e) {
                    rackContext.log(RackLogger.ERROR, "failed to read rackup from '"+ rackup + "' (" + e + ")");
                    throw new RackInitializationException("failed to read rackup input", e);
                }
            }
        }

        return this.rackupScript = rackup;
    }
   
    private void configureDefaults() {
        // configure (default) jruby.rack.request.size.[...] parameters :
        final RackConfig config = rackContext.getConfig();
        Integer iniSize = config.getInitialMemoryBufferSize();
        if (iniSize == null) iniSize = RewindableInputStream.INI_BUFFER_SIZE;
        Integer maxSize = config.getMaximumMemoryBufferSize();
        if (maxSize == null) maxSize = RewindableInputStream.MAX_BUFFER_SIZE;
        if (iniSize.intValue() > maxSize.intValue()) iniSize = maxSize;
       
        RewindableInputStream.setDefaultInitialBufferSize(iniSize);
        RewindableInputStream.setDefaultMaximumBufferSize(maxSize);
    }
   
    private static void setupJRubyManagement() {
        final String jrubyMxEnabled = "jruby.management.enabled";
        if ( ! "false".equalsIgnoreCase( System.getProperty(jrubyMxEnabled) ) ) {
            System.setProperty(jrubyMxEnabled, "true");
        }
    }
   
}
TOP

Related Classes of org.jruby.rack.DefaultRackApplicationFactory$ApplicationObjectFactory

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.