warbler_updated 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/History.txt +411 -0
  4. data/LICENSE.txt +27 -0
  5. data/Mavenfile +32 -0
  6. data/README.rdoc +280 -0
  7. data/Rakefile +63 -0
  8. data/bin/warble +11 -0
  9. data/ext/JarMain.java +334 -0
  10. data/ext/WarMain.java +375 -0
  11. data/ext/WarblerJar.java +199 -0
  12. data/ext/WarblerJarService.java +18 -0
  13. data/lib/warbler/application.rb +104 -0
  14. data/lib/warbler/bundler_helper.rb +22 -0
  15. data/lib/warbler/config.rb +265 -0
  16. data/lib/warbler/executable_helper.rb +25 -0
  17. data/lib/warbler/gems.rb +77 -0
  18. data/lib/warbler/jar.rb +348 -0
  19. data/lib/warbler/pathmap_helper.rb +20 -0
  20. data/lib/warbler/platform_helper.rb +26 -0
  21. data/lib/warbler/rake_helper.rb +30 -0
  22. data/lib/warbler/scripts/rails.rb +5 -0
  23. data/lib/warbler/task.rb +185 -0
  24. data/lib/warbler/templates/bundler.erb +19 -0
  25. data/lib/warbler/templates/config.erb +1 -0
  26. data/lib/warbler/templates/jar.erb +11 -0
  27. data/lib/warbler/templates/jbundler.erb +2 -0
  28. data/lib/warbler/templates/rack.erb +5 -0
  29. data/lib/warbler/templates/rails.erb +1 -0
  30. data/lib/warbler/templates/war.erb +19 -0
  31. data/lib/warbler/traits/bundler.rb +157 -0
  32. data/lib/warbler/traits/gemspec.rb +79 -0
  33. data/lib/warbler/traits/jar.rb +58 -0
  34. data/lib/warbler/traits/jbundler.rb +48 -0
  35. data/lib/warbler/traits/nogemspec.rb +47 -0
  36. data/lib/warbler/traits/rack.rb +33 -0
  37. data/lib/warbler/traits/rails.rb +91 -0
  38. data/lib/warbler/traits/war.rb +260 -0
  39. data/lib/warbler/traits.rb +124 -0
  40. data/lib/warbler/version.rb +10 -0
  41. data/lib/warbler/war.rb +8 -0
  42. data/lib/warbler/web_server.rb +125 -0
  43. data/lib/warbler/zip_support.rb +13 -0
  44. data/lib/warbler.rb +40 -0
  45. data/lib/warbler_jar.jar +0 -0
  46. data/warble.rb +188 -0
  47. data/warbler.gemspec +37 -0
  48. data/web.xml.erb +57 -0
  49. metadata +188 -0
data/ext/WarMain.java ADDED
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Copyright (c) 2010-2012 Engine Yard, Inc.
3
+ * Copyright (c) 2007-2009 Sun Microsystems, Inc.
4
+ * This source code is available under the MIT license.
5
+ * See the file LICENSE.txt for details.
6
+ */
7
+
8
+ import java.lang.reflect.Method;
9
+ import java.io.InputStream;
10
+ import java.io.ByteArrayInputStream;
11
+ import java.io.SequenceInputStream;
12
+ import java.io.File;
13
+ import java.io.FileNotFoundException;
14
+ import java.io.FileOutputStream;
15
+ import java.net.URI;
16
+ import java.net.URLClassLoader;
17
+ import java.net.URL;
18
+ import java.util.Arrays;
19
+ import java.util.List;
20
+ import java.util.Properties;
21
+ import java.util.Map;
22
+ import java.util.jar.JarEntry;
23
+
24
+ /**
25
+ * Used as a Main-Class in the manifest for a .war file, so that you can run
26
+ * a .war file with <tt>java -jar</tt>.
27
+ *
28
+ * WarMain can be used with different web server libraries. WarMain expects
29
+ * to have two files present in the .war file,
30
+ * <tt>WEB-INF/webserver.properties</tt> and <tt>WEB-INF/webserver.jar</tt>.
31
+ *
32
+ * When WarMain starts up, it extracts the webserver jar to a temporary
33
+ * directory, and creates a temporary work directory for the webapp. Both
34
+ * are deleted on exit.
35
+ *
36
+ * It then reads webserver.properties into a java.util.Properties object,
37
+ * creates a URL classloader holding the jar, and loads and invokes the
38
+ * <tt>main</tt> method of the main class mentioned in the properties.
39
+ *
40
+ * An example webserver.properties follows for Jetty. The <tt>args</tt>
41
+ * property indicates the names and ordering of other properties to be used
42
+ * as command-line arguments. The special tokens <tt>{{warfile}}</tt> and
43
+ * <tt>{{webroot}}</tt> are substituted with the location of the .war file
44
+ * being run and the temporary work directory, respectively.
45
+ * <pre>
46
+ * mainclass = org.eclipse.jetty.runner.Runner
47
+ * args = args0,args1,args2,args3,args4
48
+ * props = jetty.home
49
+ * args0 = --port
50
+ * args1 = {{port}}
51
+ * args2 = --config
52
+ * args3 = {{config}}
53
+ * args4 = {{warfile}}
54
+ * jetty.home = {{webroot}}
55
+ * </pre>
56
+ *
57
+ * System properties can also be set via webserver.properties. For example,
58
+ * the following entries set <tt>jetty.home</tt> before launching the server.
59
+ * <pre>
60
+ * props = jetty.home
61
+ * jetty.home = {{webroot}}
62
+ * </pre>
63
+ */
64
+ public class WarMain extends JarMain {
65
+
66
+ static final String MAIN = '/' + WarMain.class.getName().replace('.', '/') + ".class";
67
+ static final String WEBSERVER_PROPERTIES = "/WEB-INF/webserver.properties";
68
+ static final String WEBSERVER_JAR = "/WEB-INF/webserver.jar";
69
+ static final String WEBSERVER_CONFIG = "/WEB-INF/webserver.xml";
70
+ static final String WEB_INF = "WEB-INF";
71
+ static final String META_INF = "META-INF";
72
+
73
+ /**
74
+ * jruby arguments, consider the following command :
75
+ * `java -jar rails.war --1.9 -S rake db:migrate`
76
+ * arguments == [ "--1.9" ]
77
+ * executable == "rake"
78
+ * executableArgv == [ "db:migrate" ]
79
+ */
80
+ private final String[] arguments;
81
+
82
+ /**
83
+ * null to launch webserver or != null to run a executable e.g. rake
84
+ */
85
+ private final String executable;
86
+ private final String[] executableArgv;
87
+
88
+ private File webroot;
89
+
90
+ WarMain(final String[] args) {
91
+ super(args);
92
+ final List<String> argsList = Arrays.asList(args);
93
+ final int sIndex = argsList.indexOf("-S");
94
+ if ( sIndex == -1 ) {
95
+ executable = null; executableArgv = null; arguments = null;
96
+ }
97
+ else {
98
+ if ( args.length == sIndex + 1 || args[sIndex + 1].isEmpty() ) {
99
+ throw new IllegalArgumentException("missing executable after -S");
100
+ }
101
+ arguments = argsList.subList(0, sIndex).toArray(new String[0]);
102
+ String execArg = argsList.get(sIndex + 1);
103
+ executableArgv = argsList.subList(sIndex + 2, argsList.size()).toArray(new String[0]);
104
+
105
+ if (execArg.equals("rails")) {
106
+ // The rails executable doesn't play well with ScriptingContainer, so we've packaged the
107
+ // same script that would have been generated by `rake rails:update:bin`
108
+ execArg = "./META-INF/rails.rb";
109
+ }
110
+ else if (execArg.equals("bundle") && executableArgv.length > 0 && executableArgv[0].equals("exec")) {
111
+ warn("`bundle exec' may drop out of the Warbler environment and into the system environment");
112
+ }
113
+
114
+ executable = execArg;
115
+ }
116
+ }
117
+
118
+ private URL extractWebserver() throws Exception {
119
+ this.webroot = File.createTempFile("warbler", "webroot");
120
+ this.webroot.delete();
121
+ this.webroot.mkdirs();
122
+ this.webroot = new File(this.webroot, new File(archive).getName());
123
+ debug("webroot directory is " + this.webroot.getPath());
124
+ InputStream jarStream = new URI("jar", entryPath(WEBSERVER_JAR), null).toURL().openStream();
125
+ File jarFile = File.createTempFile("webserver", ".jar");
126
+ jarFile.deleteOnExit();
127
+ FileOutputStream outStream = new FileOutputStream(jarFile);
128
+ try {
129
+ byte[] buf = new byte[4096];
130
+ int bytesRead;
131
+ while ((bytesRead = jarStream.read(buf)) != -1) {
132
+ outStream.write(buf, 0, bytesRead);
133
+ }
134
+ } finally {
135
+ jarStream.close();
136
+ outStream.close();
137
+ }
138
+ debug("webserver.jar extracted to " + jarFile.getPath());
139
+ return jarFile.toURI().toURL();
140
+ }
141
+
142
+ private Properties getWebserverProperties() throws Exception {
143
+ Properties props = new Properties();
144
+ try {
145
+ InputStream is = getClass().getResourceAsStream(WEBSERVER_PROPERTIES);
146
+ if ( is != null ) props.load(is);
147
+ } catch (Exception e) { }
148
+
149
+ String port = getSystemProperty("warbler.port", getENV("PORT"));
150
+ port = port == null ? "8080" : port;
151
+ String host = getSystemProperty("warbler.host", "0.0.0.0");
152
+ String webserverConfig = getSystemProperty("warbler.webserver_config", getENV("WARBLER_WEBSERVER_CONFIG"));
153
+ String embeddedWebserverConfig = new URI("jar", entryPath(WEBSERVER_CONFIG), null).toURL().toString();
154
+ webserverConfig = webserverConfig == null ? embeddedWebserverConfig : webserverConfig;
155
+ for ( Map.Entry entry : props.entrySet() ) {
156
+ String val = (String) entry.getValue();
157
+ val = val.replace("{{warfile}}", archive).
158
+ replace("{{port}}", port).
159
+ replace("{{host}}", host).
160
+ replace("{{config}}", webserverConfig).
161
+ replace("{{webroot}}", webroot.getAbsolutePath());
162
+ entry.setValue(val);
163
+ }
164
+
165
+ if (props.getProperty("props") != null) {
166
+ String[] propsToSet = props.getProperty("props").split(",");
167
+ for ( String key : propsToSet ) {
168
+ setSystemProperty(key, props.getProperty(key));
169
+ }
170
+ }
171
+
172
+ return props;
173
+ }
174
+
175
+ private void launchWebServer(URL jar) throws Exception {
176
+ URLClassLoader loader = new URLClassLoader(new URL[] {jar});
177
+ Thread.currentThread().setContextClassLoader(loader);
178
+ Properties props = getWebserverProperties();
179
+ String mainClass = props.getProperty("mainclass");
180
+ if (mainClass == null) {
181
+ throw new IllegalArgumentException("unknown webserver main class ("
182
+ + WEBSERVER_PROPERTIES
183
+ + " is missing 'mainclass' property)");
184
+ }
185
+ Class<?> klass = Class.forName(mainClass, true, loader);
186
+ Method main = klass.getDeclaredMethod("main", new Class[] { String[].class });
187
+ String[] newArgs = launchWebServerArguments(props);
188
+ debug("invoking webserver with: " + Arrays.deepToString(newArgs));
189
+ main.invoke(null, new Object[] { newArgs });
190
+ }
191
+
192
+ private String[] launchWebServerArguments(Properties props) {
193
+ String[] newArgs = args;
194
+
195
+ if (props.getProperty("args") != null) {
196
+ String[] insertArgs = props.getProperty("args").split(",");
197
+ newArgs = new String[args.length + insertArgs.length];
198
+ for (int i = 0; i < insertArgs.length; i++) {
199
+ newArgs[i] = props.getProperty(insertArgs[i], "");
200
+ }
201
+ System.arraycopy(args, 0, newArgs, insertArgs.length, args.length);
202
+ }
203
+
204
+ return newArgs;
205
+ }
206
+
207
+ // JarMain overrides to make WarMain "launchable"
208
+ // e.g. java -jar rails.war -S rake db:migrate
209
+
210
+ @Override
211
+ protected String getExtractEntryPath(final JarEntry entry) {
212
+ final String name = entry.getName();
213
+ final String res;
214
+ if ( name.startsWith(WEB_INF) ) {
215
+ // WEB-INF/app/controllers/application_controller.rb ->
216
+ // app/controllers/application_controller.rb
217
+ res = name.substring(WEB_INF.length());
218
+ } else if (name.startsWith(META_INF)) {
219
+ // Keep them where they are.
220
+ res = name;
221
+ } else {
222
+ // 404.html -> public/404.html
223
+ // javascripts -> public/javascripts
224
+ res = "/public/" + name;
225
+ }
226
+ return res;
227
+ }
228
+
229
+ @Override
230
+ protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
231
+ // always extract but only return class-path entry URLs :
232
+ final URL entryURL = super.extractEntry(entry, path);
233
+ return path.endsWith(".jar") && path.startsWith("/lib/") ? entryURL : null;
234
+ }
235
+
236
+ @Override
237
+ protected int launchJRuby(final URL[] jars) throws Exception {
238
+ final Object scriptingContainer = newScriptingContainer(jars);
239
+
240
+ invokeMethod(scriptingContainer, "setArgv", (Object) executableArgv);
241
+ invokeMethod(scriptingContainer, "setCurrentDirectory", extractRoot.getAbsolutePath());
242
+ initJRubyScriptingEnv(scriptingContainer);
243
+
244
+ final Object provider = invokeMethod(scriptingContainer, "getProvider");
245
+ final Object rubyInstanceConfig = invokeMethod(provider, "getRubyInstanceConfig");
246
+
247
+ invokeMethod(rubyInstanceConfig, "setUpdateNativeENVEnabled", new Class[] { Boolean.TYPE }, false);
248
+
249
+ final CharSequence execScriptEnvPre = executableScriptEnvPrefix();
250
+
251
+ final String executablePath = locateExecutable(scriptingContainer, execScriptEnvPre);
252
+ if ( executablePath == null ) {
253
+ throw new IllegalStateException("failed to locate gem executable: '" + executable + "'");
254
+ }
255
+ invokeMethod(scriptingContainer, "setScriptFilename", executablePath);
256
+
257
+ invokeMethod(rubyInstanceConfig, "processArguments", (Object) arguments);
258
+
259
+ Object runtime = invokeMethod(scriptingContainer, "getRuntime");
260
+
261
+ debug("loading resource: " + executablePath);
262
+ Object executableInput =
263
+ new SequenceInputStream(new ByteArrayInputStream(execScriptEnvPre.toString().getBytes()),
264
+ (InputStream) invokeMethod(rubyInstanceConfig, "getScriptSource"));
265
+
266
+ debug("invoking " + executablePath + " with: " + Arrays.toString(executableArgv));
267
+
268
+ Object outcome = invokeMethod(runtime, "runFromMain",
269
+ new Class[] { InputStream.class, String.class },
270
+ executableInput, executablePath
271
+ );
272
+ return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
273
+ }
274
+
275
+ @Deprecated
276
+ protected String locateExecutable(final Object scriptingContainer) throws Exception {
277
+ if ( executable == null ) {
278
+ throw new IllegalStateException("no executable");
279
+ }
280
+ final File exec = new File(extractRoot, executable);
281
+ if ( exec.exists() ) {
282
+ return exec.getAbsolutePath();
283
+ }
284
+ else {
285
+ final String script = locateExecutableScript(executable, executableScriptEnvPrefix());
286
+ return (String) invokeMethod(scriptingContainer, "runScriptlet", script);
287
+ }
288
+ }
289
+
290
+ protected String locateExecutable(final Object scriptingContainer, final CharSequence envPreScript)
291
+ throws Exception {
292
+ if ( executable == null ) {
293
+ throw new IllegalStateException("no executable");
294
+ }
295
+ final File exec = new File(extractRoot, executable);
296
+ if ( exec.exists() ) {
297
+ return exec.getAbsolutePath();
298
+ }
299
+ else {
300
+ final String script = locateExecutableScript(executable, envPreScript);
301
+ return (String) invokeMethod(scriptingContainer, "runScriptlet", script);
302
+ }
303
+ }
304
+
305
+ protected CharSequence executableScriptEnvPrefix() {
306
+ final String gemsDir = new File(extractRoot, "gems").getAbsolutePath();
307
+ final String gemfile = new File(extractRoot, "Gemfile").getAbsolutePath();
308
+ debug("setting GEM_HOME to " + gemsDir);
309
+ debug("... and BUNDLE_GEMFILE to " + gemfile);
310
+
311
+ // ideally this would look up the config.override_gem_home setting
312
+ return "ENV['GEM_HOME'] = ENV['GEM_PATH'] = '"+ gemsDir +"' \n" +
313
+ "ENV['BUNDLE_GEMFILE'] ||= '"+ gemfile +"' \n" +
314
+ "require 'uri:classloader:/META-INF/init.rb'";
315
+ }
316
+
317
+ protected String locateExecutableScript(final String executable, final CharSequence envPreScript) {
318
+ return ( envPreScript == null ? "" : envPreScript + " \n" ) +
319
+ "begin\n" + // locate the executable within gemspecs :
320
+ " require 'rubygems' unless defined?(Gem) \n" +
321
+ " begin\n" + // add bundled gems to load path :
322
+ " require 'bundler' \n" +
323
+ " rescue LoadError\n" + // bundler not used
324
+ " else\n" +
325
+ " env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] \n" + // init.rb sets ENV['RAILS_ENV'] ||= ...
326
+ " env ? Bundler.setup(:default, env) : Bundler.setup(:default) \n" +
327
+ " end if ENV_JAVA['warbler.bundler.setup'] != 'false' \n" + // java -Dwarbler.bundler.setup=false -jar my.war -S pry
328
+ " exec = '"+ executable +"' \n" +
329
+ " spec = Gem::Specification.find { |s| s.executables.include?(exec) } \n" +
330
+ " spec ? spec.bin_file(exec) : nil \n" +
331
+ // returns the full path to the executable
332
+ "rescue SystemExit => e\n" +
333
+ " e.status\n" +
334
+ "end";
335
+ }
336
+
337
+ protected void initJRubyScriptingEnv(Object scriptingContainer) throws Exception {
338
+ // for some reason, the container needs to run a scriptlet in order for it
339
+ // to be able to find the gem executables later
340
+ invokeMethod(scriptingContainer, "runScriptlet", "SCRIPTING_CONTAINER_INITIALIZED=true");
341
+
342
+ invokeMethod(scriptingContainer, "setHomeDirectory", "uri:classloader:/META-INF/jruby.home");
343
+ }
344
+
345
+ @Override
346
+ protected int start() throws Exception {
347
+ if ( executable == null ) {
348
+ try {
349
+ URL server = extractWebserver();
350
+ launchWebServer(server);
351
+ }
352
+ catch (FileNotFoundException e) {
353
+ final String msg = e.getMessage();
354
+ if ( msg != null && msg.contains("WEB-INF/webserver.jar") ) {
355
+ System.out.println("specify the -S argument followed by the bin file to run e.g. `java -jar rails.war -S rake -T` ...");
356
+ System.out.println("(or if you'd like your .war file to start a web server package it using `warbler executable war`)");
357
+ }
358
+ throw e;
359
+ }
360
+ return 0;
361
+ }
362
+ return super.start();
363
+ }
364
+
365
+ @Override
366
+ public void run() {
367
+ super.run();
368
+ if ( webroot != null ) delete(webroot.getParentFile());
369
+ }
370
+
371
+ public static void main(String[] args) {
372
+ doStart(new WarMain(args));
373
+ }
374
+
375
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Copyright (c) 2010-2012 Engine Yard, Inc.
3
+ * Copyright (c) 2007-2009 Sun Microsystems, Inc.
4
+ * This source code is available under the MIT license.
5
+ * See the file LICENSE.txt for details.
6
+ */
7
+
8
+ import java.io.Closeable;
9
+ import java.io.File;
10
+ import java.io.FileInputStream;
11
+ import java.io.FileNotFoundException;
12
+ import java.io.FileOutputStream;
13
+ import java.io.IOException;
14
+ import java.io.InputStream;
15
+ import java.util.regex.Matcher;
16
+ import java.util.regex.Pattern;
17
+ import java.util.zip.ZipEntry;
18
+ import java.util.zip.ZipInputStream;
19
+ import java.util.zip.ZipOutputStream;
20
+
21
+ import org.jruby.Ruby;
22
+ import org.jruby.RubyArray;
23
+ import org.jruby.RubyHash;
24
+ import org.jruby.RubyModule;
25
+ import org.jruby.RubyString;
26
+ import org.jruby.anno.JRubyMethod;
27
+ import org.jruby.runtime.Block;
28
+ import org.jruby.runtime.ThreadContext;
29
+ import org.jruby.runtime.builtin.IRubyObject;
30
+ import org.jruby.util.ByteList;
31
+ import org.jruby.util.JRubyFile;
32
+
33
+ public class WarblerJar {
34
+ public static void create(Ruby runtime) {
35
+ RubyModule task = runtime.getClassFromPath("Warbler::Jar");
36
+ task.defineAnnotatedMethods(WarblerJar.class);
37
+ }
38
+
39
+ @JRubyMethod
40
+ public static IRubyObject create_jar(ThreadContext context, IRubyObject self,
41
+ IRubyObject jar_path, IRubyObject entries) {
42
+ final Ruby runtime = context.runtime;
43
+
44
+ if (!(entries instanceof RubyHash)) {
45
+ throw runtime.newArgumentError("expected a hash for the second argument");
46
+ }
47
+
48
+ RubyHash hash = (RubyHash) entries;
49
+ try {
50
+ FileOutputStream file = newFile(jar_path);
51
+ try {
52
+ ZipOutputStream zip = new ZipOutputStream(file);
53
+ addEntries(context, zip, hash);
54
+ zip.finish();
55
+ } finally {
56
+ close(file);
57
+ }
58
+ } catch (IOException e) {
59
+ if (runtime.isDebug()) {
60
+ e.printStackTrace(runtime.getOut());
61
+ }
62
+ throw runtime.newIOErrorFromException(e);
63
+ }
64
+
65
+ return runtime.getNil();
66
+ }
67
+
68
+ @JRubyMethod
69
+ public static IRubyObject entry_in_jar(ThreadContext context, IRubyObject self,
70
+ IRubyObject jar_path, IRubyObject entry) {
71
+ final Ruby runtime = context.runtime;
72
+ try {
73
+ InputStream entryStream = getStream(jar_path.convertToString().getUnicodeValue(),
74
+ entry.convertToString().getUnicodeValue());
75
+ try {
76
+ byte[] buf = new byte[16384];
77
+ ByteList bytes = new ByteList();
78
+ int bytesRead;
79
+ while ((bytesRead = entryStream.read(buf)) != -1) {
80
+ bytes.append(buf, 0, bytesRead);
81
+ }
82
+ IRubyObject stringio = runtime.getModule("StringIO");
83
+ return stringio.callMethod(context, "new", runtime.newString(bytes));
84
+ } finally {
85
+ close(entryStream);
86
+ }
87
+ } catch (IOException e) {
88
+ if (runtime.isDebug()) {
89
+ e.printStackTrace(runtime.getOut());
90
+ }
91
+ throw runtime.newIOErrorFromException(e);
92
+ }
93
+ }
94
+
95
+ private static void addEntries(ThreadContext context, ZipOutputStream zip, RubyHash entries) throws IOException {
96
+ RubyArray keys = entries.keys().sort(context, Block.NULL_BLOCK);
97
+ for (int i = 0; i < keys.getLength(); i++) {
98
+ IRubyObject key = keys.entry(i);
99
+ IRubyObject value = entries.op_aref(context, key);
100
+ addEntry(context, zip, key.convertToString().getUnicodeValue(), value);
101
+ }
102
+ }
103
+
104
+ private static void addEntry(ThreadContext context, ZipOutputStream zip, String entryName, IRubyObject value) throws IOException {
105
+ if (value.respondsTo("read")) {
106
+ RubyString str = (RubyString) value.callMethod(context, "read").checkStringType();
107
+ ByteList strByteList = str.getByteList();
108
+ byte[] contents = strByteList.getUnsafeBytes();
109
+ zip.putNextEntry(new ZipEntry(entryName));
110
+ zip.write(contents, strByteList.getBegin(), strByteList.getRealSize());
111
+ } else {
112
+ File f;
113
+ if (value.isNil() || (f = getFile(value)).isDirectory()) {
114
+ zip.putNextEntry(new ZipEntry(entryName + "/"));
115
+ } else {
116
+ String path = f.getPath();
117
+ if (!f.exists()) {
118
+ path = value.convertToString().getUnicodeValue();
119
+ }
120
+
121
+ try {
122
+ InputStream inFile = getStream(path, null);
123
+ try {
124
+ zip.putNextEntry(new ZipEntry(entryName));
125
+ byte[] buf = new byte[16384];
126
+ int bytesRead;
127
+ while ((bytesRead = inFile.read(buf)) != -1) {
128
+ zip.write(buf, 0, bytesRead);
129
+ }
130
+ } finally {
131
+ close(inFile);
132
+ }
133
+ } catch (IOException e) {
134
+ System.err.println("File not found; " + path + " not in archive");
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ private static FileOutputStream newFile(IRubyObject jar_path) throws IOException {
141
+ return new FileOutputStream(getFile(jar_path));
142
+ }
143
+
144
+ private static File getFile(IRubyObject path) {
145
+ return JRubyFile.create(path.getRuntime().getCurrentDirectory(),
146
+ path.convertToString().getUnicodeValue());
147
+ }
148
+
149
+ private static void close(Closeable c) {
150
+ try {
151
+ c.close();
152
+ } catch (Exception e) {
153
+ }
154
+ }
155
+
156
+ private static final Pattern PROTOCOL = Pattern.compile("^[a-z][a-z0-9]+:");
157
+
158
+ private static InputStream getStream(String jar, String entry) throws IOException {
159
+ Matcher m = PROTOCOL.matcher(jar);
160
+ while (m.find()) {
161
+ jar = jar.substring(m.end());
162
+ m = PROTOCOL.matcher(jar);
163
+ }
164
+
165
+ String[] path = jar.split("!/");
166
+ InputStream stream = new FileInputStream(path[0]);
167
+ for (int i = 1; i < path.length; i++) {
168
+ stream = entryInJar(stream, path[i]);
169
+ }
170
+
171
+ if (entry == null) {
172
+ return stream;
173
+ }
174
+
175
+ return entryInJar(stream, entry);
176
+ }
177
+
178
+ private static String trimTrailingSlashes(String path) {
179
+ if (path.endsWith("/")) {
180
+ return path.substring(0, path.length() - 1);
181
+ } else {
182
+ return path;
183
+ }
184
+ }
185
+
186
+ private static InputStream entryInJar(InputStream jar, String entry) throws IOException {
187
+ entry = trimTrailingSlashes(entry);
188
+
189
+ ZipInputStream jstream = new ZipInputStream(jar);
190
+ ZipEntry zentry;
191
+ while ((zentry = jstream.getNextEntry()) != null) {
192
+ if (trimTrailingSlashes(zentry.getName()).equals(entry)) {
193
+ return jstream;
194
+ }
195
+ jstream.closeEntry();
196
+ }
197
+ throw new FileNotFoundException("entry '" + entry + "' not found in " + jar);
198
+ }
199
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) 2010-2012 Engine Yard, Inc.
3
+ * Copyright (c) 2007-2009 Sun Microsystems, Inc.
4
+ * This source code is available under the MIT license.
5
+ * See the file LICENSE.txt for details.
6
+ */
7
+
8
+ import java.io.IOException;
9
+ import org.jruby.Ruby;
10
+ import org.jruby.runtime.load.BasicLibraryService;
11
+
12
+ public class WarblerJarService implements BasicLibraryService {
13
+ public boolean basicLoad(final Ruby runtime) throws IOException {
14
+ WarblerJar.create(runtime);
15
+ return true;
16
+ }
17
+ }
18
+
@@ -0,0 +1,104 @@
1
+ #--
2
+ # Copyright (c) 2010-2012 Engine Yard, Inc.
3
+ # Copyright (c) 2007-2009 Sun Microsystems, Inc.
4
+ # This source code is available under the MIT license.
5
+ # See the file LICENSE.txt for details.
6
+ #++
7
+
8
+ require 'rake'
9
+
10
+ # Extension of Rake::Application that allows the +warble+ command to
11
+ # report its name properly and inject its own tasks without a
12
+ # Rakefile.
13
+ class Warbler::Application < Rake::Application
14
+ include Warbler::RakeHelper
15
+
16
+ def initialize
17
+ super
18
+ Warbler.application = self
19
+ @project_loaded = false
20
+ end
21
+
22
+ # Sets the application name and loads Warbler's own tasks
23
+ def load_rakefile
24
+ @name = 'warble'
25
+
26
+ # Load the main warbler tasks
27
+ wt = Warbler::Task.new
28
+
29
+ task :default => wt.name
30
+
31
+ desc "Generate a configuration file to customize your archive"
32
+ task :config => "#{wt.name}:config"
33
+
34
+ desc "Install Warbler tasks in your Rails application"
35
+ task :pluginize => "#{wt.name}:pluginize"
36
+
37
+ desc "Feature: package gem repository inside a jar"
38
+ task :gemjar => "#{wt.name}:gemjar"
39
+
40
+ desc "Feature: make an executable archive (runnable + an embedded web server)"
41
+ task :executable => "#{wt.name}:executable"
42
+
43
+ desc "Feature: make a runnable archive (e.g. java -jar rails.war -S rake db:migrate)"
44
+ task :runnable => "#{wt.name}:runnable"
45
+
46
+ desc "Feature: precompile all Ruby files"
47
+ task :compiled => "#{wt.name}:compiled"
48
+
49
+ desc "Display version of Warbler"
50
+ task :version => "#{wt.name}:version"
51
+ end
52
+
53
+ # Loads the project Rakefile in a separate application
54
+ def load_project_rakefile
55
+ return if @project_loaded
56
+ # Load any application rakefiles to aid in autodetecting applications
57
+ set_application Warbler.project_application = Rake::Application.new
58
+ Rake::Application::DEFAULT_RAKEFILES.each do |rf|
59
+ if File.exist?(rf)
60
+ begin
61
+ load rf
62
+ rescue LoadError
63
+ load File.join(Dir.getwd, rf)
64
+ end
65
+ break
66
+ end
67
+ end
68
+ set_application
69
+ @project_loaded = true
70
+ end
71
+
72
+ # Run the application: The equivalent code for the +warble+ command
73
+ # is simply <tt>Warbler::Application.run</tt>.
74
+ def self.run; new.run end
75
+
76
+ # Run the application.
77
+ def run
78
+ set_application
79
+ super
80
+ end
81
+
82
+ # Remap the version option to display Warbler version.
83
+ def standard_rake_options
84
+ super.map do |opt|
85
+ if opt.first == '--version'
86
+ ['--version', '-V', "Display the program version.",
87
+ lambda { |value|
88
+ puts "Warbler version #{Warbler::VERSION}"
89
+ exit
90
+ }
91
+ ]
92
+ else
93
+ opt
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def set_application(app = self)
101
+ Rake.application = app
102
+ end
103
+
104
+ end