Skip to content

Commit

Permalink
leverage the classloader + osgi-bundle support to ScriptingContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
mkristian committed May 30, 2015
1 parent 9e79159 commit 2bc5a0e
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 92 deletions.
104 changes: 12 additions & 92 deletions core/src/main/java/org/jruby/embed/IsolatedScriptingContainer.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
package org.jruby.embed;

import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.osgi.framework.Bundle;

/**
* the IsolatedScriptingContainer detects the whether it is used with
* a Thread.currentThread.contextClassLoader (J2EE) or with the classloader
* which loaded IsolatedScriptingContainer.class (OSGi case)
*
* the setup of LOAD_PATH and GEM_PATH and JRUBY_HOME uses ONLY uri: or uri:classloader:
* protocol paths. i.e. everything lives within one or more classloaders - no jars added from
* jave.class.path or similar "magics"
*
* the root of the "main" classloader is add to LOAD_PATH and GEM_PATH.
*
* in the OSGi case there are helper methods to add ClassLoaders to the LOAD_PATH or GEM_PATH
* the IsolatedScriptingContainer does set GEM_HOME and GEM_PATH and JARS_HOME
* in such a way that it uses only resources which can be reached with classloader.
*
* a typical setup for the ContextClassLoader case and OSGi case looks likes this:
* <li>LOAD_PATH == [ "uri:classloader:/META-INF/jruby.home/lib/ruby/1.9/site_ruby",
* "uri:classloader:/META-INF/jruby.home/lib/ruby/shared",
* "uri:classloader:/META-INF/jruby.home/lib/ruby/1.9",
* "uri:classloader:" ]</li>
* <li>Gem::Specification.dirs == [ "uri:classloader:/specifications", "uri:classloader:/META-INF/jruby.home/lib/ruby/gems/shared/specifications" ]
* here very resource is loaded via <code>Thread.currentTHread.getContextClassLoader().getResourceAsStream(...)</code>
* GEM_HOME is uri:classloader://META-INF/jruby.home/lib/ruby/gems/shared
* GEM_PATH is uri:classloader://
* JARS_HOME is uri:classloader://jars
*
* <code>new URL( uri ).openStream()</code>, i.e. <code>new URL(classloader.getResource().toString()).openStream()</code> has to work for
* those classloaders. felix, knoplerfish and equinox OSGi framework do work.
*
* NOTE: <code>Gem.path</code> is base for determine the <code>Gem::Specification.dirs</code> and <code>Gem::Specification.dirs</code> is
* used to find gemspec files of the installed gems.
* but whenever you want to set them via {@link #setEnvironment(Map)} this will be honored.
*/
public class IsolatedScriptingContainer extends ScriptingContainer {

private static final String JRUBYDIR = "/.jrubydir";
private static final String JRUBY_HOME = "/META-INF/jruby.home";

public IsolatedScriptingContainer()
Expand Down Expand Up @@ -74,75 +53,16 @@ public IsolatedScriptingContainer( LocalContextScope scope,

@Override
public void setEnvironment(Map environment) {
if (environment == null || !environment.containsKey("GEM_PATH")) {
Map env = environment == null ? new HashMap() : new HashMap(environment);
env.put("GEM_PATH", "");
if (environment == null || !environment.containsKey("GEM_PATH")
|| !environment.containsKey("GEM_HOME")|| !environment.containsKey("JARS_HOME")) {
Map<String,String> env = environment == null ? new HashMap<String,String>() : new HashMap<String,String>(environment);
if (!env.containsKey("GEM_PATH")) env.put("GEM_PATH", "uri:classloader://");
if (!env.containsKey("GEM_HOME")) env.put("GEM_HOME", "uri:classloader:/" + JRUBY_HOME);
if (!env.containsKey("JARS_HOME")) env.put("JARS_HOME", "uri:classloader://jars");
super.setEnvironment(env);
}
else {
super.setEnvironment(environment);
}
}

public void addLoadPath( ClassLoader cl ) {
addLoadPath( cl, JRUBYDIR );
}

public void addLoadPath( ClassLoader cl, String ref ) {
addLoadPath(createUri(cl, ref));
}

public void addBundleToLoadPath( Bundle cl ) {
addBundleToLoadPath( cl, JRUBYDIR );
}

public void addBundleToLoadPath( Bundle cl, String ref ) {
addLoadPath(createUriFromBundle(cl, ref));
}

private String createUriFromBundle( Bundle cl, String ref) {
URL url = cl.getResource( ref );
if ( url == null && ref.startsWith( "/" ) ) {
url = cl.getResource( ref.substring( 1 ) );
}
if ( url == null ) {
throw new RuntimeException( "reference " + ref + " not found on bundle " + cl );
}
return "uri:" + url.toString().replaceFirst( ref + "$", "" );
}

private void addLoadPath(String uri) {
runScriptlet( "$LOAD_PATH << '" + uri + "' unless $LOAD_PATH.member?( '" + uri + "' )" );
}

public void addBundleToGemPath( Bundle cl ) {
addBundleToGemPath( cl, "/specifications" + JRUBYDIR );
}

public void addBundleToGemPath( Bundle cl, String ref ) {
addGemPath(createUriFromBundle(cl, ref));
}

public void addGemPath( ClassLoader cl ) {
addGemPath( cl, "/specifications" + JRUBYDIR );
}

public void addGemPath( ClassLoader cl, String ref ) {
addGemPath(createUri(cl, ref));
}

private String createUri(ClassLoader cl, String ref) {
URL url = cl.getResource( ref );
if ( url == null && ref.startsWith( "/" ) ) {
url = cl.getResource( ref.substring( 1 ) );
}
if ( url == null ) {
throw new RuntimeException( "reference " + ref + " not found on classloader " + cl );
}
return "uri:" + url.toString().replaceFirst( ref + "$", "" );
}

private void addGemPath(String uri) {
runScriptlet( "require 'rubygems/defaults/jruby';Gem::Specification.add_dir '" + uri + "' unless Gem::Specification.dirs.member?( '" + uri + "' )" );
}
}
67 changes: 67 additions & 0 deletions core/src/main/java/org/jruby/embed/ScriptingContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@
package org.jruby.embed;

import java.io.UnsupportedEncodingException;

import org.jruby.embed.internal.LocalContextProvider;

import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jruby.CompatVersion;
import org.jruby.Profile;
import org.jruby.Ruby;
Expand Down Expand Up @@ -68,6 +72,7 @@
import org.jruby.util.KCode;
import org.jruby.util.cli.OutputStrings;
import org.jruby.util.cli.Options;
import org.osgi.framework.Bundle;

/**
* ScriptingContainer provides various methods and resources that are useful
Expand Down Expand Up @@ -1886,4 +1891,66 @@ public void setClassloaderDelegate(boolean classloaderDelegate) {
public boolean getClassloaderDelegate() {
return getProvider().getRubyInstanceConfig().isClassloaderDelegate();
}

/**
* add the given classloader to the LOAD_PATH
* @param classloader
*/
public void addLoadPath(ClassLoader classloader) {
addLoadPath(createUri(classloader, "/.jrubydir"));
}

/**
* add the classloader from the given bundle to the LOAD_PATH
* @param bundle
*/
public void addBundleToLoadPath(Bundle bundle) {
addLoadPath(createUriFromBundle(bundle, "/.jrubydir"));
}

private String createUriFromBundle(Bundle cl, String ref) {
URL url = cl.getResource( ref );
if ( url == null && ref.startsWith( "/" ) ) {
url = cl.getResource( ref.substring( 1 ) );
}
if ( url == null ) {
throw new RuntimeException( "reference " + ref + " not found on bundle " + cl );
}
return "uri:" + url.toString().replaceFirst( ref + "$", "" );
}

private void addLoadPath(String uri) {
runScriptlet( "$LOAD_PATH << '" + uri + "' unless $LOAD_PATH.member?( '" + uri + "' )" );
}

/**
* add the classloader from the given bundle to the GEM_PATH
* @param bundle
*/
public void addBundleToGemPath(Bundle bundle) {
addGemPath(createUriFromBundle(bundle, "/specifications/.jrubydir"));
}

/**
* add the given classloader to the GEM_PATH
* @param classloader
*/
public void addGemPath(ClassLoader classloader) {
addGemPath(createUri(classloader, "/specifications/.jrubydir"));
}

private String createUri(ClassLoader cl, String ref) {
URL url = cl.getResource( ref );
if ( url == null && ref.startsWith( "/" ) ) {
url = cl.getResource( ref.substring( 1 ) );
}
if ( url == null ) {
throw new RuntimeException( "reference " + ref + " not found on classloader " + cl );
}
return "uri:" + url.toString().replaceFirst( ref + "$", "" );
}

private void addGemPath(String uri) {
runScriptlet( "require 'rubygems/defaults/jruby';Gem::Specification.add_dir '" + uri + "' unless Gem::Specification.dirs.member?( '" + uri + "' )" );
}
}

0 comments on commit 2bc5a0e

Please sign in to comment.