Created
January 13, 2012 12:20
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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..."); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package test; | |
// Our bridge interface for JRuby | |
public interface JRubyInterface { | |
void createRuntime(); | |
Object eval(String scriptlet); | |
void terminate(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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