Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 20 additions & 40 deletions core/src/main/java/org/jruby/embed/IsolatedScriptingContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,16 @@
*
* in the OSGi case there are helper methods to add ClassLoaders to the LOAD_PATH or GEM_PATH
*
* a typical setup for the ContextClassLoader case looks likes this:
* 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:", "uri:classloader:/META-INF/jruby.home/lib/ruby/gems/shared" ]
* <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>
*
* a typical setup for OSGi case (one bundle with everything):
* <li>LOAD_PATH == [ "uri:bundle://16.0:1/META-INF/jruby.home/lib/ruby/1.9/site_ruby",
* "uri:bundle://16.0:1/META-INF/jruby.home/lib/ruby/shared",
* "uri:bundle://16.0:1/META-INF/jruby.home/lib/ruby/1.9",
* "uri:bundle://16.0:1" ]</li>
* <li>Gem::Specification.dirs == [ "uri:bundle://16.0:1", "uri:bundle://16.0:1/META-INF/jruby.home/lib/ruby/gems/shared" ]
* other OSGi frameworks use other uris like bundleresource:/16.fwk1661197821. here very resource is loaded via
* <code>new URL( uri )openStream()</code>, i.e. <code>new URL(classloader.getResource().toString()).openStream()</code> has to work for
* those classloaders. felix and equinox OSGi framework do work.
* <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.
Expand All @@ -43,7 +36,6 @@ public class IsolatedScriptingContainer extends ScriptingContainer {

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

public IsolatedScriptingContainer()
{
Expand All @@ -70,37 +62,25 @@ public IsolatedScriptingContainer( LocalContextScope scope,
LocalVariableBehavior behavior,
boolean lazy )
{
super( scope, behavior, lazy );
URL home = Thread.currentThread().getContextClassLoader().getResource( JRUBY_HOME_DIR.substring( 1 ) );
final String baseuri;
if ( home == null ) {
home = this.getClass().getClassLoader().getResource( JRUBY_HOME_DIR );
if ( home == null ) {
throw new RuntimeException( "BUG can not find " + JRUBY_HOME_DIR );
}
setClassLoader( this.getClass().getClassLoader() );
setHomeDirectory( "uri:" + home.toString().replaceFirst( JRUBYDIR + "$", "" ) );
baseuri = createUri(getClassLoader(), "/jruby/java.rb" );
}
else {
setHomeDirectory( "uri:classloader:" + JRUBY_HOME );
baseuri = "uri:classloader:/";
}
super(scope, behavior, lazy);

// get the right classloader
ClassLoader cl = this.getClass().getClassLoader();
if (cl == null) cl = Thread.currentThread().getContextClassLoader();
setClassLoader( cl );

setLoadPaths( Arrays.asList( "uri:classloader:" ) );

// clean up LOAD_PATH
getProvider().getRubyInstanceConfig().setLoadPaths(Arrays.asList(baseuri));
runScriptlet( "$LOAD_PATH.delete_if{|p| p =~ /jar$/ };"
// TODO NormalizedFile does too much - should leave uri: files as they are
+ "$LOAD_PATH.each{|p| p.sub!( /:\\/([^\\/])/,'://\\1' )}" );

runScriptlet( "require 'rubygems/defaults/jruby';" // make sure we have the monkey patch Gem::Specification
// set the right jruby home
setHomeDirectory( "uri:classloader:" + JRUBY_HOME );

// setup the isolated GEM_PATH, i.e. without $HOME/.gem/**
runScriptlet("require 'rubygems/defaults/jruby';"
+ "Gem::Specification.reset;"
+ "Gem::Specification.add_dir '" + getHomeDirectory() + "/lib/ruby/gems/shared';"
// if jruby-core and jruby-stdlib comes from the same osgi bundle, assume the embedded gems
// are in the same bundle
+ (getHomeDirectory().startsWith(baseuri) ? "Gem::Specification.add_dir '" + baseuri + "';" : "" ) );
+ "Gem::Specification.add_dir 'uri:classloader:" + JRUBY_HOME + "/lib/ruby/gems/shared';"
+ "Gem::Specification.add_dir 'uri:classloader:';");
}

public void addLoadPath( ClassLoader cl ) {
addLoadPath( cl, JRUBYDIR );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private void loadJar(Ruby runtime, boolean wrap) {
}
else if (location.startsWith(URLResource.URI)){
url = null;
runtime.getJRubyClassLoader().addURLNoIndex(URLResource.getResourceURL(location));
runtime.getJRubyClassLoader().addURLNoIndex(URLResource.getResourceURL(runtime, location));
}
else {
File f = new File(location);
Expand Down
15 changes: 10 additions & 5 deletions core/src/main/java/org/jruby/runtime/load/LoadService.java
Original file line number Diff line number Diff line change
Expand Up @@ -1003,12 +1003,17 @@ private Library findLibraryBySearchState(SearchState state) {
return state.library;
}

// TODO(ratnikov): Remove the special classpath case by introducing a classpath file resource
Library library = findLibraryWithClassloaders(state, state.searchFile, state.suffixType);
if (library != null) {
state.library = library;
// this test is needed to avoid an in org.jruby.runtime.load.LoadService.findFileInClasspath
// running inside felix-4.2.1 osgi framework
if (loadPath.size() == 0 || !loadPath.first().asJavaString().equals("uri:classloader:")) {
// TODO(ratnikov): Remove the special classpath case by introducing a classpath file resource
Library library = findLibraryWithClassloaders(state, state.searchFile, state.suffixType);
if (library != null) {
state.library = library;
}
return library;
}
return library;
return null;
}

@Deprecated
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/java/org/jruby/util/JRubyFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ public static FileResource createResource(ThreadContext context, String pathname
}

public static FileResource createResource(Ruby runtime, String pathname) {
return createResource(runtime.getPosix(), runtime.getCurrentDirectory(), pathname);
return createResource(runtime.getPosix(), runtime, runtime.getCurrentDirectory(), pathname);
}

public static FileResource createResource(POSIX posix, String cwd, String pathname) {
return createResource(posix, null, cwd, pathname);
}

public static FileResource createResource(POSIX posix, Ruby runtime, String cwd, String pathname) {
FileResource emptyResource = EmptyFileResource.create(pathname);
if (emptyResource != null) return emptyResource;

Expand All @@ -80,7 +84,7 @@ public static FileResource createResource(POSIX posix, String cwd, String pathna
if (pathname.startsWith("classpath:")) return ClasspathResource.create(pathname);

// replace is needed for maven/jruby-complete/src/it/app_using_classpath_uri to work
if (pathname.startsWith("uri:")) return URLResource.create(pathname.replace("classpath:/", ""));
if (pathname.startsWith("uri:")) return URLResource.create(runtime, pathname.replace("classpath:/", ""));

if (pathname.startsWith("file:")) {
pathname = pathname.substring(5);
Expand Down
57 changes: 31 additions & 26 deletions core/src/main/java/org/jruby/util/URLResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import jnr.posix.FileStat;

import org.jruby.Ruby;
import org.jruby.util.io.ChannelDescriptor;
import org.jruby.util.io.ModeFlags;

Expand All @@ -31,19 +32,21 @@ public class URLResource implements FileResource {
private final String pathname;

private final JarFileStat fileStat;
private final ClassLoader cl;

URLResource(String uri, URL url, String[] files) {
this(uri, url, null, files);
this(uri, url, null, null, files);
}

URLResource(String uri, String pathname, String[] files) {
this(uri, null, pathname, files);
URLResource(String uri, ClassLoader cl, String pathname, String[] files) {
this(uri, null, cl, pathname, files);
}

private URLResource(String uri, URL url, String pathname, String[] files) {
private URLResource(String uri, URL url, ClassLoader cl, String pathname, String[] files) {
this.uri = uri;
this.list = files;
this.url = url;
this.cl = cl;
this.pathname = pathname;
this.fileStat = new JarFileStat(this);
}
Expand Down Expand Up @@ -126,7 +129,7 @@ public FileStat lstat() {

@Override
public JRubyFile hackyGetJRubyFile() {
return JRubyNonExistentFile.NOT_EXIST;
return JRubyNonExistentFile.NOT_EXIST;
}

@Override
Expand All @@ -135,7 +138,7 @@ public InputStream openInputStream()
try
{
if (pathname != null) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(pathname);
return cl.getResourceAsStream(pathname);
}
return url.openStream();
}
Expand All @@ -153,33 +156,32 @@ public ChannelDescriptor openDescriptor(ModeFlags flags, int perm) throws Resour
return new ChannelDescriptor(openInputStream(), flags);
}

public static FileResource createClassloaderURI(String pathname) {
public static FileResource createClassloaderURI(Ruby runtime, String pathname) {
// retrieve the classloader from the runtime if available otherwise mimic how the runtime got its classloader
// and use this classloader
ClassLoader cl = runtime != null ? runtime.getJRubyClassLoader() : URLResource.class.getClassLoader();
if (cl == null ) {
cl = Thread.currentThread().getContextClassLoader();
}
if (pathname.startsWith("/")) {
pathname = pathname.substring(1);
}
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathname);
if (is != null) {
try
{
is.close();
}
// need Exception here due to strange NPE in some cases
catch (Exception e) {}
}
String[] files = listClassLoaderFiles(pathname);
URL url = cl.getResource(pathname);
String[] files = listClassLoaderFiles(cl, pathname);
return new URLResource(URI_CLASSLOADER + pathname,
is == null ? null : pathname,
cl,
url == null ? null : pathname,
files);
}

public static FileResource create(String pathname)
public static FileResource create(Ruby runtime, String pathname)
{
if (!pathname.startsWith(URI)) {
return null;
}
pathname = pathname.substring(URI.length());
if (pathname.startsWith(CLASSLOADER)) {
return createClassloaderURI(pathname.substring(CLASSLOADER.length()));
return createClassloaderURI(runtime, pathname.substring(CLASSLOADER.length()));
}
return createRegularURI(pathname);
}
Expand All @@ -189,7 +191,10 @@ private static FileResource createRegularURI(String pathname) {
try
{
// TODO NormalizedFile does too much - should leave uri: files as they are
// and make file:/a protocol to be file:///a so the second replace does not apply
pathname = pathname.replaceFirst( "file:/([^/])", "file:///$1" );
pathname = pathname.replaceFirst( ":/([^/])", "://$1" );

url = new URL(pathname);
// we do not want to deal with those url here like this though they are valid url/uri
if (url.getProtocol().startsWith("http")){
Expand All @@ -198,6 +203,7 @@ private static FileResource createRegularURI(String pathname) {
}
catch (MalformedURLException e)
{
e.printStackTrace();
// file does not exists
return new URLResource(URI + pathname, (URL)null, null);
}
Expand Down Expand Up @@ -251,13 +257,13 @@ private static String[] listFilesFromInputStream(InputStream is) {
}
}
}
private static String[] listClassLoaderFiles(String pathname) {
private static String[] listClassLoaderFiles(ClassLoader cl, String pathname) {
if (pathname.endsWith(".rb") || pathname.endsWith(".class") || pathname.endsWith(".jar")) {
return null;
}
try
{
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(pathname + "/.jrubydir");
Enumeration<URL> urls = cl.getResources(pathname + "/.jrubydir");
if (!urls.hasMoreElements()) {
return null;
}
Expand All @@ -284,8 +290,7 @@ private static String[] listFiles(String pathname) {
}
try
{
// TODO remove this replace
InputStream is = new URL(pathname.replace("file://", "file:/") + "/.jrubydir").openStream();
InputStream is = new URL(pathname + "/.jrubydir").openStream();
// no inputstream happens with knoplerfish OSGI and osgi tests from /maven/jruby-complete
if (is != null) {
return listFilesFromInputStream(is);
Expand All @@ -300,10 +305,10 @@ private static String[] listFiles(String pathname) {
}
}

public static URL getResourceURL(String location)
public static URL getResourceURL(Ruby runtime, String location)
{
if (location.startsWith(URI + CLASSLOADER)){
return Thread.currentThread().getContextClassLoader().getResource(location.substring(URI_CLASSLOADER.length()));
return runtime.getJRubyClassLoader().getResource(location.substring(URI_CLASSLOADER.length()));
}
try
{
Expand Down
60 changes: 52 additions & 8 deletions core/src/test/java/org/jruby/util/URLResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public class URLResourceTest extends TestCase {

public void testDirectory(){
String uri = Thread.currentThread().getContextClassLoader().getResource( "somedir" ).toExternalForm();
// hmm not sure why the url from the classloader does not work :(
FileResource resource = URLResource.create( "uri:" + uri.replace( "file://", "file:/" ));
FileResource resource = URLResource.create( null, "uri:" + uri);

assertNotNull(resource );
assertFalse(resource.isFile());
Expand All @@ -21,8 +20,7 @@ public void testDirectory(){

public void testNoneDirectory(){
String uri = Thread.currentThread().getContextClassLoader().getResource( "somedir/dir_without_listing" ).toExternalForm();
// TODO once the URLResource does keep the protocol part of the uri as is we can remove this replace
FileResource resource = URLResource.create( "uri:" + uri.replace( "file:/", "file:///" ));
FileResource resource = URLResource.create( null, "uri:" + uri);

assertNotNull(resource );
// you can open streams on file-system directories
Expand All @@ -34,8 +32,7 @@ public void testNoneDirectory(){

public void testFile(){
String uri = Thread.currentThread().getContextClassLoader().getResource( "somedir/.jrubydir" ).toExternalForm();
// TODO once the URLResource does keep the protocol part of the uri as is we can remove this replace
FileResource resource = URLResource.create( "uri:" + uri.replace( "file:/", "file:///" ));
FileResource resource = URLResource.create( null, "uri:" + uri);

assertNotNull(resource );
// you can open streams on file-system directories
Expand All @@ -47,13 +44,60 @@ public void testFile(){

public void testNonExistingFile(){
String uri = Thread.currentThread().getContextClassLoader().getResource( "somedir" ).toExternalForm();
// TODO once the URLResource does keep the protocol part of the uri as is we can remove this replace
FileResource resource = URLResource.create( "uri:" + uri.replace( "file:/", "file:///" ) + "/not_there");
FileResource resource = URLResource.create( null, "uri:" + uri + "/not_there");

assertNotNull(resource );
assertFalse(resource.isFile());
assertFalse(resource.exists());
assertFalse(resource.isDirectory());
assertNull(resource.list());
}

public void testDirectoryClassloader()
{
FileResource resource = URLResource.create( null, "uri:classloader:/somedir");

assertNotNull( resource );
assertFalse( resource.isFile() );
assertTrue( resource.isDirectory() );
assertTrue( resource.exists() );
assertEquals( Arrays.asList( resource.list() ),
Arrays.asList( new String[] { ".", "dir_without_listing",
"dir_with_listing" } ) );
}

public void testNoneDirectoryClassloader()
{
FileResource resource = URLResource.create( null, "uri:classloader:/somedir/dir_without_listing");

assertNotNull( resource );
// you can open streams on file-system directories
assertTrue( resource.isFile() );
assertTrue( resource.exists() );
assertFalse( resource.isDirectory() );
assertNull( resource.list() );
}

public void testFileClassloader()
{
FileResource resource = URLResource.create( null, "uri:classloader:/somedir/.jrubydir" );

assertNotNull( resource );
// you can open streams on file-system directories
assertTrue( resource.isFile() );
assertTrue( resource.exists() );
assertFalse( resource.isDirectory() );
assertNull( resource.list() );
}

public void testNonExistingFileClassloader()
{
FileResource resource = URLResource.create( null, "uri:classloader:/somedir/not_there" );

assertNotNull( resource );
assertFalse( resource.isFile() );
assertFalse( resource.exists() );
assertFalse( resource.isDirectory() );
assertNull( resource.list() );
}
}
Loading