Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java -jar rails.war -S rake db:migrate #136

Merged
merged 14 commits into from Feb 5, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 1 addition & 4 deletions Gemfile
@@ -1,9 +1,6 @@
source "http://rubygems.org/"

gem "rake"
gem "rubyzip"
gem "jruby-jars"
gem "jruby-rack"
gemspec

group :development do
gem "jruby-openssl", :platform => :jruby
Expand Down
7 changes: 4 additions & 3 deletions Rakefile
Expand Up @@ -7,9 +7,10 @@

begin
require 'bundler/setup'
rescue LoadError
puts $!
puts "Please install Bundler and run 'bundle install' to ensure you have all dependencies"
rescue LoadError => e
require('rubygems') && retry
puts "Please `gem install bundler' and run `bundle install' to ensure you have all dependencies"
raise e
end

require 'bundler/gem_helper'
Expand Down
223 changes: 152 additions & 71 deletions ext/JarMain.java
Expand Up @@ -8,111 +8,164 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class JarMain implements Runnable {
public static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";

private String[] args;
private String path, jarfile;
private boolean debug;
private File extractRoot;

public JarMain(String[] args) throws Exception {

static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";

final boolean debug = isDebug();

protected final String[] args;
protected final String archive;
private final String path;

protected File extractRoot;

JarMain(String[] args) {
this.args = args;
URL mainClass = getClass().getResource(MAIN);
this.path = mainClass.toURI().getSchemeSpecificPart();
this.jarfile = this.path.replace("!" + MAIN, "").replace("file:", "");
this.debug = isDebug();
this.extractRoot = File.createTempFile("jruby", "extract");
this.extractRoot.delete();
this.extractRoot.mkdirs();
try {
this.path = mainClass.toURI().getSchemeSpecificPart();
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
archive = this.path.replace("!" + MAIN, "").replace("file:", "");

Runtime.getRuntime().addShutdownHook(new Thread(this));
}

protected URL[] extractArchive() throws Exception {
final JarFile jarFile = new JarFile(archive);
try {
Map<String, JarEntry> jarNames = new HashMap<String, JarEntry>();
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
String extractPath = getExtractEntryPath(entry);
if ( extractPath != null ) jarNames.put(extractPath, entry);
}

extractRoot = File.createTempFile("jruby", "extract");
extractRoot.delete(); extractRoot.mkdirs();

private URL[] extractJRuby() throws Exception {
JarFile jf = new JarFile(this.jarfile);
List<String> jarNames = new ArrayList<String>();
for (Enumeration<JarEntry> eje = jf.entries(); eje.hasMoreElements(); ) {
String name = eje.nextElement().getName();
if (name.startsWith("META-INF/lib") && name.endsWith(".jar")) {
jarNames.add("/" + name);
final List<URL> urls = new ArrayList<URL>();
for (Map.Entry<String, JarEntry> e : jarNames.entrySet()) {
URL entryURL = extractEntry(e.getValue(), e.getKey());
if (entryURL != null) urls.add( entryURL );
}
return (URL[]) urls.toArray(new URL[urls.size()]);
}

List<URL> urls = new ArrayList<URL>();
for (String name : jarNames) {
urls.add(extractJar(name));
finally {
jarFile.close();
}

return (URL[]) urls.toArray(new URL[urls.size()]);
}

private URL extractJar(String jarpath) throws Exception {
InputStream jarStream = new URI("jar", path.replace(MAIN, jarpath), null).toURL().openStream();
String jarname = jarpath.substring(jarpath.lastIndexOf("/") + 1, jarpath.lastIndexOf("."));
File jarFile = new File(extractRoot, jarname + ".jar");
jarFile.deleteOnExit();
FileOutputStream outStream = new FileOutputStream(jarFile);
protected String getExtractEntryPath(final JarEntry entry) {
final String name = entry.getName();
if ( name.startsWith("META-INF/lib") && name.endsWith(".jar") ) {
return name.substring(name.lastIndexOf("/") + 1);
}
return null; // do not extract entry
}

protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
final File file = new File(extractRoot, path);
if ( entry.isDirectory() ) {
file.mkdirs();
return null;
}
final String entryPath = entryPath(entry.getName());
final InputStream entryStream;
try {
entryStream = new URI("jar", entryPath, null).toURL().openStream();
}
catch (IllegalArgumentException e) {
// TODO gems '%' file name "encoding" ?!
debug("failed to open jar:" + entryPath + " skipping entry: " + entry.getName(), e);
return null;
}
final File parent = file.getParentFile();
if ( parent != null ) parent.mkdirs();
FileOutputStream outStream = new FileOutputStream(file);
final byte[] buf = new byte[65536];
try {
byte[] buf = new byte[65536];
int bytesRead = 0;
while ((bytesRead = jarStream.read(buf)) != -1) {
while ((bytesRead = entryStream.read(buf)) != -1) {
outStream.write(buf, 0, bytesRead);
}
} finally {
jarStream.close();
}
finally {
entryStream.close();
outStream.close();
file.deleteOnExit();
}
debug(jarname + ".jar extracted to " + jarFile.getPath());
return jarFile.toURI().toURL();
if (false) debug(entry.getName() + " extracted to " + file.getPath());
return file.toURI().toURL();
}

protected String entryPath(String name) {
if ( ! name.startsWith("/") ) name = "/" + name;
return path.replace(MAIN, name);
}

private int launchJRuby(URL[] jars) throws Exception {
protected Object newScriptingContainer(final URL[] jars) throws Exception {
System.setProperty("org.jruby.embed.class.path", "");
URLClassLoader loader = new URLClassLoader(jars);
ClassLoader loader = new URLClassLoader(jars);
Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, loader);
Object scriptingContainer = scriptingContainerClass.newInstance();

Method argv = scriptingContainerClass.getDeclaredMethod("setArgv", new Class[] {String[].class});
argv.invoke(scriptingContainer, new Object[] {args});
Method setClassLoader = scriptingContainerClass.getDeclaredMethod("setClassLoader", new Class[] {ClassLoader.class});
setClassLoader.invoke(scriptingContainer, new Object[] {loader});
debug("invoking " + jarfile + " with: " + Arrays.deepToString(args));

Method runScriptlet = scriptingContainerClass.getDeclaredMethod("runScriptlet", new Class[] {String.class});
return ((Number) runScriptlet.invoke(scriptingContainer, new Object[] {
"begin\n" +
"require 'META-INF/init.rb'\n" +
"require 'META-INF/main.rb'\n" +
"0\n" +
"rescue SystemExit => e\n" +
"e.status\n" +
"end"
})).intValue();
debug("scripting container class loader urls: " + Arrays.toString(jars));
invokeMethod(scriptingContainer, "setArgv", (Object) args);
invokeMethod(scriptingContainer, "setClassLoader", new Class[] { ClassLoader.class }, loader);
return scriptingContainer;
}

protected int launchJRuby(final URL[] jars) throws Exception {
final Object scriptingContainer = newScriptingContainer(jars);
debug("invoking " + archive + " with: " + Arrays.deepToString(args));
Object outcome = invokeMethod(scriptingContainer, "runScriptlet", launchScript());
return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
}

private int start() throws Exception {
URL[] u = extractJRuby();
return launchJRuby(u);
protected String launchScript() {
return
"begin\n" +
" require 'META-INF/init.rb'\n" +
" require 'META-INF/main.rb'\n" +
" 0\n" +
"rescue SystemExit => e\n" +
" e.status\n" +
"end";
}

protected int start() throws Exception {
final URL[] jars = extractArchive();
return launchJRuby(jars);
}

private void debug(String msg) {
if (debug) {
System.out.println(msg);
}
protected void debug(String msg) {
debug(msg, null);
}

private void delete(File f) {
protected void debug(String msg, Throwable t) {
if (debug) System.out.println(msg);
if (debug && t != null) t.printStackTrace(System.out);
}

protected void delete(File f) {
if (f.isDirectory()) {
File[] children = f.listFiles();
for (int i = 0; i < children.length; i++) {
Expand All @@ -121,30 +174,57 @@ private void delete(File f) {
}
f.delete();
}

public void run() {
delete(extractRoot);
if ( extractRoot != null ) delete(extractRoot);
}

public static void main(String[] args) {
doStart(new JarMain(args));
}

protected static void doStart(final JarMain main) {
try {
int exit = new JarMain(args).start();
if(isSystemExitEnabled()) System.exit(exit);
} catch (Exception e) {
System.err.println("error: " + e.toString());
Throwable t = e;
while (t.getCause() != null && t.getCause() != t) {
t = t.getCause();
}

if (isDebug()) {
t.printStackTrace();
}
System.exit(1);
}
}

protected static Object invokeMethod(final Object self, final String name, final Object... args)
throws NoSuchMethodException, IllegalAccessException, Exception {

final Class[] signature = new Class[args.length];
for ( int i = 0; i < args.length; i++ ) signature[i] = args[i].getClass();
return invokeMethod(self, name, signature, args);
}

private static boolean isDebug() {
return System.getProperty("warbler.debug") != null;
protected static Object invokeMethod(final Object self, final String name, final Class[] signature, final Object... args)
throws NoSuchMethodException, IllegalAccessException, Exception {
Method method = self.getClass().getDeclaredMethod(name, signature);
try {
return method.invoke(self, args);
}
catch (InvocationTargetException e) {
Throwable target = e.getTargetException();
if (target instanceof Exception) {
throw (Exception) target;
}
throw e;
}
}

static boolean isDebug() {
return Boolean.getBoolean("warbler.debug");
}

/**
Expand All @@ -155,4 +235,5 @@ private static boolean isDebug() {
private static boolean isSystemExitEnabled(){
return System.getProperty("warbler.skip_system_exit") == null; //omission enables System.exit use
}

}