Skip to content

Commit

Permalink
Make JarUtil work with custom classloaders
Browse files Browse the repository at this point in the history
Added the ability for users to set a "resolver" in JarUtil
that lets it find resources that are loaded by a custom classloader.
This is needed in OSGi apps (like Eclipse RCP apps), since OSGi
resources do not have simple jar: URLs (they use a custom
protocol called bundleresource:).
  • Loading branch information
WadeWalker committed Feb 11, 2013
1 parent 3084174 commit f3894c9
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 1 deletion.
24 changes: 23 additions & 1 deletion src/java/com/jogamp/common/util/JarUtil.java
Expand Up @@ -52,6 +52,26 @@
public class JarUtil {
private static final boolean DEBUG = Debug.debug("JarUtil");

/**
* This interface allows users to provide an URL resolver that will convert custom classloader
* URLs like Eclipse/OSGi "bundleresource:" URLs to normal "jar:" URLs. This is needed when
* the classloader has been customized.
*/
public interface Resolver {
URL resolve(URL url);
}

/** If non-null, we use this to resolve class file URLs after querying them from the classloader. */
private static Resolver resolver;

/**
* Setter.
* @param r Resolver to use after querying class file URLs from the classloader.
*/
public static void setResolver(Resolver r) {
resolver = r;
}

/**
* Returns <code>true</code> if the Class's <code>"com.jogamp.common.GlueGenVersion"</code>
* is loaded from a JarFile and hence has a Jar URL like
Expand Down Expand Up @@ -92,7 +112,9 @@ public static URL getJarURL(String clazzBinName, ClassLoader cl) throws IllegalA
if(null == clazzBinName || null == cl) {
throw new IllegalArgumentException("null arguments: clazzBinName "+clazzBinName+", cl "+cl);
}
final URL url = IOUtil.getClassURL(clazzBinName, cl);
URL url = IOUtil.getClassURL(clazzBinName, cl);
if(resolver != null)
url = resolver.resolve(url);
// test name ..
final String urlS = url.toExternalForm();
if(DEBUG) {
Expand Down
77 changes: 77 additions & 0 deletions src/junit/com/jogamp/common/util/TestJarUtil.java
Expand Up @@ -29,10 +29,12 @@
package com.jogamp.common.util;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.JarURLConnection;
import java.net.URLStreamHandler;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
Expand Down Expand Up @@ -168,6 +170,81 @@ public void testJarUtilJarInJar02() throws IOException, ClassNotFoundException {
System.err.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXX");
}

/**
* Tests JarUtil's ability to resolve non-JAR URLs with a custom resolver. Meant to be used
* in cases like an OSGi plugin, where all classes are loaded with custom classloaders and
* therefore return URLs that don't start with "jar:". Adapted from test 02 above.
*/
@Test
public void testJarUtilJarInJar03() throws IOException, ClassNotFoundException {
System.err.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXX");

Assert.assertTrue(TempJarCache.initSingleton());
Assert.assertTrue(TempCacheReg.isTempJarCacheUsed());
Assert.assertTrue(TempJarCache.isInitialized());

/** This classloader mimics what OSGi's does -- it takes jar: URLs and makes them into bundleresource: URLs
* where the JAR is not directly accessible anymore. Here I leave the JAR name at the end of the URL so I can
* retrieve it later in the resolver, but OSGi obscures it completely and returns URLs like
* "bundleresource:4.fwk1990213994:1/Something.class" where the JAR name not present. */
class CustomClassLoader extends ClassLoader {
CustomClassLoader() {
super(TestJarUtil.this.getClass().getClassLoader());
}

/** Override normal method to return un-resolvable URL. */
public URL getResource(String name) {
URL url = super.getResource(name);
if(url == null)
return(null);
URL urlReturn = null;
try {
// numbers to mimic OSGi -- can be anything
urlReturn = new URL("bundleresource", "4.fwk1990213994", 1, url.getFile(),
new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
});
} catch(MalformedURLException e) {
// shouldn't happen, since I create the URL correctly above
Assert.assertTrue(false);
}
return(urlReturn);
}
};

/* This resolver converts bundleresource: URLs back into jar: URLs. OSGi does this by consulting
* opaque bundle data inside its custom classloader to find the stored JAR path; we do it here
* by simply retrieving the JAR name from where we left it at the end of the URL. */
JarUtil.setResolver( new JarUtil.Resolver() {
public URL resolve( URL url ) {
if(url.toString().startsWith("bundleresource")) {
try {
return(new URL("jar", "", url.getFile()));
} catch(IOException e) {
return(url);
}
}
else
return(url);
}
} );

final ClassLoader rootCL = new CustomClassLoader();

// Get containing JAR file "TestJarsInJar.jar" and add it to the TempJarCache
TempJarCache.addAll(GlueGenVersion.class, JarUtil.getJarFileURL("ClassInJar0", rootCL));

// Fetch and load the contained "ClassInJar1.jar"
final URL ClassInJar2_jarFileURL = JarUtil.getJarFileURL(TempJarCache.getResource("sub/ClassInJar2.jar"));
final ClassLoader cl = new URLClassLoader(new URL[] { ClassInJar2_jarFileURL }, rootCL);
Assert.assertNotNull(cl);
validateJarUtil("ClassInJar2.jar", "ClassInJar2", cl);
System.err.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXX");
}

public static void main(String args[]) throws IOException {
String tstname = TestJarUtil.class.getName();
org.junit.runner.JUnitCore.main(tstname);
Expand Down

0 comments on commit f3894c9

Please sign in to comment.