Skip to content

Instantly share code, notes, and snippets.

@MLeo
Created January 13, 2012 12:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MLeo/1605839 to your computer and use it in GitHub Desktop.
Save MLeo/1605839 to your computer and use it in GitHub Desktop.
Evading the dotCMS ClassLoader (or any other ClassLoader), in this case because dotCMS contains a copy of jruby-1.3.1.jar and I need jruby-complete-1.6.5.1.jar, this example assumes the required additional plugin files are provided.
package test;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.IdentityHashMap;
import java.util.Map;
// Sorry about the name, it was after 1AM when I came up with this class and the implementation...
public class EvadeThatPeskyClassLoaderClassLoader extends URLClassLoader {
// These classes are our interface with the world outside this classloader, preferably classes that you control.
// The classloader will return these classes when requested.
// The use for them is that we can get instances that we can use without reflection.
private final Class<?>[] bridgeClasses;
public EvadeThatPeskyClassLoaderClassLoader(URL[] urls, Class<?> ... bridgeClasses) {
super(urls, getSystemClassLoader());
this.bridgeClasses = bridgeClasses;
}
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Map<Class<?>, Void> queue = new IdentityHashMap<Class<?>, Void>();
Map<Class<?>, Void> processed = new IdentityHashMap<Class<?>, Void>();
for (Class<?> c : bridgeClasses) {
queue.put(c, null);
}
// Here we will traverse the entire
while (!queue.isEmpty()) {
Class<?> clazz = queue.keySet().iterator().next();
queue.remove(clazz);
if (processed.containsKey(clazz)) {
continue;
}
processed.put(clazz, null);
if (clazz.getName().equals(name)) {
return clazz;
}
for (Class<?> i : clazz.getInterfaces()) {
queue.put(i, null);
}
Class<?> sup = clazz.getSuperclass();
if (sup != null) {
queue.put(sup, null);
}
}
// If none of the bridge classes, or their interfaces and superclasses match, load the class as we should.
return super.loadClass(name, resolve);
}
// A simple helper method that ensures we load the right thing, to prevent class cast exceptions or pesky reflection.
public <T> T initializeBridgeInstance(String implName, Class<T> bridgeClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
for (Class<?> c : bridgeClasses) {
if (c == bridgeClass || bridgeClass.isAssignableFrom(c)) {
Class<?> implClass = loadClass(implName);
if (bridgeClass.isAssignableFrom(implClass)) {
return bridgeClass.cast(implClass.newInstance());
}
}
}
throw new IllegalArgumentException(bridgeClass + " is not a bridge class (or superclass of one), as such you won't ever get a working instance.");
}
}
package test;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jruby.Ruby;
// This servlet (add it to your web-ext.xml!) will
@SuppressWarnings("serial")
public class FirstStage extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// We determine some paths, this assumes that the tomcat directory of dotCMS is the working directory. Change the appropiate.
final File pluginDir = new File("../plugins/JRubyTest").getAbsoluteFile();
final File jrubyJar = new File(pluginDir, "lib/jruby-complete-1.6.5.1.jar");
final File pluginJar = new File(pluginDir, "build/jar/plugin-JRubyTest.jar");
System.out.println("Original SecondStage classloader: " + SecondStage.class.getClassLoader());
System.out.println("Original Ruby classloader (the wrong one!): " + Ruby.class.getClassLoader());
final EvadeThatPeskyClassLoaderClassLoader jRubyLoader = new EvadeThatPeskyClassLoaderClassLoader(new URL[]{jrubyJar.toURI().toURL(), pluginJar.toURI().toURL()}, JRubyInterface.class);
try {
JRubyInterface ruby = jRubyLoader.initializeBridgeInstance(SecondStage.class.getName(), JRubyInterface.class);
ruby.createRuntime();
try {
ruby.eval("puts 'Hello from a happier jRuby world!'");
ruby.eval("puts 'We will be requiring rubygems and compass next.'");
ruby.eval("require 'rubygems';require 'compass'");
ruby.eval("puts 'If we got here, then everything worked fine and we managed to load our version of jruby.'");
} finally {
ruby.terminate();
}
} catch (Throwable e) {
throw new ServletException(e);
}
resp.getWriter().println("Guess it worked...");
}
}
package test;
// Our bridge interface for JRuby
public interface JRubyInterface {
void createRuntime();
Object eval(String scriptlet);
void terminate();
}
package test;
import java.io.File;
import java.util.Arrays;
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.javasupport.JavaEmbedUtils;
// This is the 'second stage', an implementation of a bridge class
public class SecondStage implements JRubyInterface {
{
// This shows that we have a 'copy' of our desired classes.
System.out.println("This: " + SecondStage.class.getClassLoader());
System.out.println("Ruby: " + Ruby.class.getClassLoader());
}
private Ruby ruby;
@Override
public void createRuntime() {
// Determine the lib directory for our plugin
final File libPath = new File("../plugins/JRubyTest/lib");
System.out.println("SecondStage.createRuntime(): " + libPath.getAbsolutePath());
RubyInstanceConfig config = new RubyInstanceConfig();
// Add the lib directory to the load path of JRuby
config.loadPaths().add(libPath.getAbsolutePath());
// Require the compass-gems jar, this is the jarred up contents of java -jar jruby-complete-1.6.5.1.jar -S gem install -i ./compass-gems compass
config.requiredLibraries().add("compass-gems.jar");
ruby = JavaEmbedUtils.initialize(Arrays.asList(), config);
}
@Override
public void terminate() {
JavaEmbedUtils.terminate(ruby);
}
@Override
public Object eval(String scriptlet) {
return ruby.evalScriptlet(scriptlet);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment