warbler_updated 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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