warbler 1.3.6 → 1.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,3 +4,5 @@ doc
4
4
  .bundle
5
5
  nbproject
6
6
  target
7
+ Gemfile.lock
8
+ .idea
data/.travis.yml CHANGED
@@ -1,12 +1,25 @@
1
1
  rvm:
2
- - 1.8.7
3
- - 1.9.2
4
- - jruby-18mode
5
- # NOTE: seems to be failing on Travis the same way as jruby-rack did, stack
6
- # seems to overflow when gems are loaded (does not happen locally :() :
7
- # SystemStackError: stack level too deep
8
- # ./spec/../lib/warbler/traits/bundler.rb:30:in `after_configure'
9
- #- jruby-19mode
2
+ - jruby
3
+ - jruby-head
10
4
  branches:
11
5
  only:
12
6
  - master
7
+ env:
8
+ - JRUBY_OPTS="--1.8 --server -Xcext.enabled=false -Xcompile.invokedynamic=false"
9
+ - JRUBY_OPTS="--1.9 --server -Xcext.enabled=false -Xcompile.invokedynamic=false"
10
+ matrix:
11
+ include:
12
+ - rvm: 1.8.7
13
+ env: ''
14
+ - rvm: 1.9.2
15
+ env: ''
16
+ - rvm: 1.9.3
17
+ env: ''
18
+ notifications:
19
+ irc:
20
+ channels:
21
+ - "irc.freenode.org#jruby"
22
+ on_success: change
23
+ on_failure: always
24
+ template:
25
+ - "%{repository} (%{branch}:%{commit} by %{author}): %{message} (%{build_url})"
data/Gemfile CHANGED
@@ -1,15 +1,12 @@
1
1
  source "http://rubygems.org/"
2
2
 
3
- gem "rake"
4
- gem "rubyzip"
5
- gem "jruby-jars"
6
- gem "jruby-rack"
3
+ gemspec
7
4
 
8
5
  group :development do
9
6
  gem "jruby-openssl", :platform => :jruby
10
- gem "rspec"
7
+ gem "rspec", ">= 2.8.0"
11
8
  gem "diff-lcs"
12
- gem "rcov", ">= 0.9.8"
13
- gem "rdoc"
9
+ gem "rcov", ">= 0.9.8", :platform => :mri_18
10
+ gem "rdoc", ">= 2.4.2"
14
11
  gem "childprocess", :platform => :mri
15
12
  end
data/LICENSE.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Warbler is provided under the terms of the MIT license.
4
4
 
5
- Warbler (c) 2010-2012 Engine Yard, Inc.
5
+ Warbler (c) 2010-2013 Engine Yard, Inc.
6
6
  Warbler (c) 2007-2009 Sun Microsystems, Inc.
7
7
 
8
8
  Permission is hereby granted, free of charge, to any person
data/README.rdoc CHANGED
@@ -1,9 +1,8 @@
1
- = Warbler
1
+ = Warbler {<img src="https://travis-ci.org/jruby/warbler.png" />}[https://travis-ci.org/jruby/warbler]
2
2
 
3
- Warbler is a gem to make a Java jar or war file out of any Ruby,
4
- Rails, Merb, or Rack application. Warbler provides a minimal,
5
- flexible, Ruby-like way to bundle up all of your application files for
6
- deployment to a Java environment.
3
+ Warbler is a gem to make a Java jar or war file out of any Ruby, Rails or Rack
4
+ application. Warbler provides a minimal, flexible, Ruby-like way to bundle up
5
+ all of your application files for deployment to a Java environment.
7
6
 
8
7
  Warbler provides a sane set of out-of-the box defaults that should allow most
9
8
  Ruby applications to assemble and Just Work.
@@ -14,7 +13,8 @@ Ruby applications to assemble and Just Work.
14
13
 
15
14
  2. Run warbler in the top directory of your application: <tt>warble</tt>.
16
15
 
17
- 3a. For a web project, deploy your myapp.war file to your favorite Java application server.
16
+ 3a. For a web project, deploy your +myapp.war+ file to your favorite Java
17
+ application server.
18
18
 
19
19
  3b. For a standalone applications, just run it: <tt>java -jar myapp.jar</tt>.
20
20
 
@@ -26,22 +26,24 @@ defined tasks.
26
26
  $ warble -T
27
27
  warble compiled # Feature: precompile all Ruby files
28
28
  warble config # Generate a configuration file to customize your archive
29
- warble executable # Feature: make an executable archive
30
- warble gemjar # Feature: package gem repository inside a war
29
+ warble executable # Feature: make an executable archive (runnable + an emb...
30
+ warble gemjar # Feature: package gem repository inside a jar
31
31
  warble pluginize # Install Warbler tasks in your Rails application
32
+ warble runnable # Feature: make a runnable archive (e.g. java -jar rails...
32
33
  warble version # Display version of Warbler
33
34
  warble war # Create the project war file
34
35
  warble war:clean # Remove the project war file
35
36
  warble war:debug # Dump diagnostic information
36
37
 
38
+
37
39
  Type <tt>warble</tt> to create the jar or war file.
38
40
 
39
41
  == Features
40
42
 
41
- Warbler "features" are small Rake tasks that run before the creation
42
- of the war file and make manipulations to the archive structure. For
43
- instance, the +executable+ feature makes your war file capable of
44
- running on its own, without a servlet container:
43
+ Warbler "features" are small Rake tasks that run before the creation of the war
44
+ file and make manipulations to the archive structure. For instance, the
45
+ +executable+ feature makes your war file capable of running on its own,
46
+ without a servlet container (using an embedded web server) :
45
47
 
46
48
  warble executable war
47
49
 
@@ -53,83 +55,81 @@ or configure them in <tt>config/warble.rb</tt> to always be used.
53
55
 
54
56
  config.features = %w(FEATURE)
55
57
 
56
- Currently, three features are available.
58
+ Currently, the following features are available :
57
59
 
58
60
  * +gemjar+: This bundles all gems into a single gem file to reduce the
59
61
  number of files in the .war. This is mostly useful for Google
60
62
  AppEngine where the number of files per application has a limit.
61
63
  (Note: not applicable for jar-based applications.)
62
- * +executable+: This bundles an embedded web server into the .war so
63
- that it can either be deployed into a traditional java web server or
64
- run as a standalone application using <tt>java -jar myapp.war</tt>.
64
+ * +runnable+: This makes a (standard Java) runnable .war archive thus you can
65
+ execute binary bundled (gem) commands e.g. "rake". You should use the -S
66
+ switch to specify the binary followed by any arguments in takes e.g.
67
+ <tt>java -jar myrailsapp.war -S rake db:migrate</tt>.
68
+ * +executable+: This bundles an embedded web server into the .war so that it
69
+ can either be deployed into a traditional java web server or run as a
70
+ standalone application using <tt>java -jar myapp.war</tt>.
65
71
  (Note: jar-based applications are executable by default.)
66
- * +compiled+: This uses +jrubyc+ to precompile all .rb files in your
67
- application to .class files and includes those in the .war instead
68
- of the Ruby sources. NOTE: The war file will still contain .rb
69
- files, but they will be short stubs containing the following code:
70
-
71
- require __FILE__.sub(/\.rb$/, '.class')
72
+ * +compiled+: This uses +jrubyc+ to precompile all .rb files in your application
73
+ to .class files and includes those in the .war instead of the Ruby sources.
74
+ NOTE: The war file will still contain .rb files, but they will be short stubs
75
+ containing the following code : <tt>load __FILE__.sub(/\.rb$/, '.class')</tt>
72
76
 
73
- Features may form the basis for a third-party plugin system in the
74
- future if there is demand.
77
+ Features may form the basis for a third-party plugin system (in the future)
78
+ if there is demand.
75
79
 
76
- NOTE: Feature tasks must be included in the same command invocation
77
- and inserted before the +war+ task in order to take effect. For
78
- example, <tt>warble compiled; warble war</tt> does not compile and
79
- obfuscate +.rb+ sources because the second invocation of +warble+ does
80
- not run the +compiled+ feature and creates a basic war with the
81
- sources included. Always make sure you run
80
+ NOTE: Feature tasks must be included in the same command invocation and
81
+ inserted before the +war+ task in order to take effect. For example,
82
+ <tt>warble compiled; warble war</tt> does not compile and obfuscate +.rb+
83
+ sources because the second invocation of +warble+ does not run the +compiled+
84
+ feature and creates a basic war with the sources included, make sure you run :
82
85
 
83
86
  warble compiled war
84
87
 
85
- or, if it's important that the war always be compiled, use the option
86
- above to put the feature in your <tt>config/warble.rb</tt>.
88
+ or, if it's important that the war always be compiled, use the option above to
89
+ put the feature in your <tt>config/warble.rb</tt>.
87
90
 
88
91
  == War or Jar?
89
92
 
90
- War-based projects are for Rails, Merb, or Rack-based web
91
- applications. They usually contain a <tt>config/environment.rb</tt>
92
- file, a <tt>config/init.rb</tt> file, or a <tt>config.ru</tt> file.
93
- The presence of these files are used to determine if the project
94
- is a web application, and thus a Java EE compatible war file is built
95
- for the project.
93
+ War-based projects are for Rails, Merb, or Rack-based web applications.
94
+ They usually contain a +config/environment.rb+ file, a +config/init.rb+ file,
95
+ or a +config.ru+ file.
96
+ The presence of these files are used to determine if the project is a web
97
+ application, and thus a Java EE compatible war file is built for the project.
96
98
 
97
- Jar-based projects are for standalone Ruby applications. Usually a
98
- Ruby application has a launcher script in the <tt>bin</tt> directory
99
- and Ruby code in the <tt>lib</tt> directory. Warbler packages the
100
- application so that <tt>java -jar myapp.jar</tt> runs the launcher
101
- script.
99
+ Jar-based projects are for standalone Ruby applications. Usually a Ruby
100
+ application has a launcher script in the +bin+ directory and Ruby code
101
+ in the <tt>lib</tt> directory. Warbler packages the application so that
102
+ <tt>java -jar myapp.jar</tt> runs the launcher script.
102
103
 
103
104
  == Jar Files
104
105
 
105
106
  === Gem Specification (gemspec) Files
106
107
 
107
- If your project has a <tt>.gemspec</tt> file in the top directory, it
108
- will be used to configure the project's dependencies, launcher script,
109
- require paths, and the files to be included in the archive. For best
110
- results make sure your gemspec specifies all of the following
111
- attributes:
108
+ If your project has a <tt>.gemspec</tt> file in the top directory, it will be
109
+ used to configure the project's dependencies, launcher script, require paths,
110
+ and the files to be included in the archive. For best results make sure your
111
+ gemspec specifies all of the following attributes:
112
112
 
113
113
  * +executables+
114
114
  * +require_paths+
115
115
  * runtime dependencies added with +add_dependency+
116
116
 
117
- If your project do not have a <tt>.gemspec</tt>, Warbler will attempt
118
- to guess the launcher from the contents of the <tt>bin</tt> directory
119
- and use the <tt>lib</tt> directory as the lone require path. All files
120
- in the project will be included in the archive.
117
+ If your project do not have a <tt>.gemspec</tt>, Warbler will attempt to guess
118
+ the launcher from the contents of the <tt>bin</tt> directory and use the
119
+ <tt>lib</tt> directory as the lone require path. All files in the project
120
+ will be included in the archive.
121
121
 
122
122
  === Bundler
123
123
 
124
- Applications that use Bundler[http://gembundler.com/], detected via
125
- presence of a +Gemfile+, will have the gems packaged up into the
126
- archive along with the Gemfile. The Bundler groups named
127
- +:development+ and +:test+ will be excluded by default, unless you
128
- specify with +config.bundle_without+ in +config/warble.rb+.
124
+ Applications that use Bundler[http://gembundler.com/], detected via presence of
125
+ a +Gemfile+, will have the gems packaged up into the archive along with the
126
+ Gemfile. The Bundler groups named ":development", ":test" and ":assets" will be
127
+ excluded by default, unless you specify with <tt>config.bundle_without</tt> in
128
+ +config/warble.rb+.
129
129
 
130
- Warbler supports Bundler for gems and git repositories, but not for
131
- plain path components. Warbler will warn when a +:path+ component is
132
- found in the +Gemfile+ and will refuse to include it in the archive.
130
+ Warbler supports Bundler for gems and git repositories, but not for plain path
131
+ components. Warbler will warn when a +:path+ component is found in the +Gemfile+
132
+ and will refuse to include it in the archive.
133
133
 
134
134
  == War Files
135
135
 
@@ -138,42 +138,34 @@ found in the +Gemfile+ and will refuse to include it in the archive.
138
138
  Rails applications are detected automatically and configured appropriately.
139
139
  The following items are set up for you:
140
140
 
141
- * Your application runs in the +production+ environment by default. Change it in <tt>config/warble.rb</tt> (see below).
141
+ * Your application runs in the +production+ environment by default.
142
+ Change it in +config/warble.rb+ (see below).
142
143
  * The Rails gem is packaged if you haven't vendored Rails (Rails <= 2.x).
143
- * Other gems configured in Rails.configuration.gems are packaged (Rails 2.1 - 2.3)
144
- * Multi-thread-safe execution (as introduced in Rails 2.2) is detected and runtime pooling is disabled.
145
-
146
- === Merb applications
147
-
148
- Merb applications are detected automatically, and the merb-core gem and its
149
- dependencies are packaged.
144
+ * Other gems configured in Rails.configuration.gems are packaged (2.1 - 2.3)
145
+ * Multi-thread-safe execution (as introduced in Rails 2.2) is detected and
146
+ runtime pooling is disabled.
150
147
 
151
148
  === Other Rack-based applications
152
149
 
153
- If you have a +config.ru+ file in the top directory or one of the
154
- immediate subdirectories of your application, it will be included and
155
- used as the rackup script for your Rack-based application. You will
156
- probably need to specify framework and application gems in
157
- config/warble.rb unless you're using Bundler to manage your gems.
158
- <tt>ENV['RACK_ENV']</tt> will be set to +production+.
150
+ If you have a +config.ru+ file in the top directory or one of the immediate
151
+ subdirectories of your application, it will be included and used as the rackup
152
+ script for your Rack-based application. You will probably need to specify
153
+ framework and application gems in +config/warble.rb+ unless you're using Bundler
154
+ to manage your gems. <tt>ENV['RACK_ENV']</tt> will be set to +production+.
159
155
 
160
156
  See {the examples in the jruby-rack project}[http://github.com/jruby/jruby-rack/tree/master/examples/]
161
157
  of how to configure Warbler to package Camping and Sinatra apps.
162
158
 
163
159
  === Configuration auto-detect notes
164
160
 
165
- * Warbler will load the +environment+ Rake task in a Rails application
166
- to try to detect some configuration. If you don't have database
167
- access in the environment where you package your application, you
168
- may wish to set +Warbler.framework_detection+ to false at the top of
169
- config.rb. In this case you may need to specify additional details
170
- such as booter, gems and other settings that would normally be
171
- gleaned from the application configuration.
172
- * A more accurate way of detecting a Merb application's gems is
173
- needed. Until then, you will have to specify them in
174
- +config/warble.rb+. See below.
175
- * Is it possible to more generally detect what gems an application
176
- uses? Gem.loaded_specs is available, but the application needs to be
161
+ * Warbler will load the +environment+ Rake task in a Rails application to try
162
+ to detect some configuration. If you don't have database access in the
163
+ environment where you package your application, you may wish to set
164
+ <tt>Warbler.framework_detection = false</tt> at the top of +config.rb+.
165
+ In this case you may need to specify additional details such as booter, gems
166
+ and other settings that would normally be gleaned from the app configuration.
167
+ * Is it possible to more generally detect what gems an application uses?
168
+ <tt>Gem.loaded_specs</tt> is available, but the application needs to be
177
169
  loaded first before its contents are reliable.
178
170
 
179
171
  == Custom configuration
@@ -185,17 +177,16 @@ doing this. With the gem, simply run
185
177
 
186
178
  warble config
187
179
 
188
- Finally, edit the config/warble.rb to your taste. The generated
189
- config/warble.rb file is fully-documented with the available options
190
- and default values.
180
+ Finally, edit the +config/warble.rb+ to your taste. The generated file is
181
+ fully-documented with the available options and default values.
191
182
 
192
183
  === War layout
193
184
 
194
185
  The default configuration puts application files (+app+, +config+, +lib+,
195
- +log+, +vendor+, +tmp+) under the .war file's WEB-INF directory, and files in
186
+ +log+, +vendor+, +tmp+) under the .war file's +WEB-INF+ directory, and files in
196
187
  +public+ in the root of the .war file. Any Java .jar files stored in lib will
197
- automatically be placed in WEB-INF/lib for placement on the web app's
198
- classpath.
188
+ automatically be placed in +WEB-INF/lib+ for placement on the web app's
189
+ class-path.
199
190
 
200
191
  === Web.xml
201
192
 
@@ -220,27 +211,27 @@ For more information on configuration, see Warbler::Config.
220
211
 
221
212
  == Rakefile integration
222
213
 
223
- If you'd like to control Warbler from your own project's Rakefile,
224
- simply add the following code somewhere in the Rakefile:
214
+ If you'd like to control Warbler from your own project's +Rakefile+,
215
+ simply add the following code somewhere in the +Rakefile+ :
225
216
 
226
217
  require 'warbler'
227
218
  Warbler::Task.new
228
219
 
229
- If you're using Bundler, you'll want to add Warbler to your Gemfile:
220
+ If you're using Bundler, you'll want to add Warbler to your +Gemfile+ :
230
221
 
231
222
  group :development do
232
223
  gem "warbler"
233
224
  end
234
225
 
235
- Now you should be able to invoke "rake war" to create your war file.
226
+ Now you should be able to invoke <tt>rake war</tt> to create your war file.
236
227
 
237
228
  == Troubleshooting
238
229
 
239
- If Warbler isn't packaging the files you were expecting, use the
240
- +war:debug+ task to give you more insight into what's going on.
230
+ If Warbler isn't packaging the files you were expecting, use the +war:debug+
231
+ task to give you more insight into what's going on.
241
232
 
242
233
  If you think you found a bug, please file one at
243
- http://kenai.com/jira/browse/WARBLER.
234
+ https://github.com/jruby/warbler/issues.
244
235
 
245
236
  == Source
246
237
 
@@ -248,22 +239,21 @@ You can get the Warbler source using Git, in any of the following ways:
248
239
 
249
240
  git clone git://git.caldersphere.net/warbler.git
250
241
  git clone git://github.com/jruby/warbler.git
251
- git clone git://kenai.com/warbler~main
252
242
 
253
243
  You can also download a tarball of Warbler source at
254
244
  https://github.com/jruby/warbler/tarball/master.
255
245
 
256
246
  == Development
257
247
 
258
- You can develop Warbler with any implementation of Ruby. To write
259
- Warbler code and run specs, you need to have Bundler installed
260
- and run "bundle install" once.
248
+ You can develop Warbler with any implementation of Ruby. To write Warbler code
249
+ and run specs, you need to have Bundler installed and run <tt>bundle</tt> once.
261
250
 
262
- After that, simply run "rake".
251
+ After that, simply run <tt>rake</tt>.
263
252
 
264
253
  == License
265
254
 
266
255
  Warbler is provided under the terms of the MIT license.
267
256
 
268
- Warbler (c) 2010-2012 Engine Yard, Inc.
257
+ Warbler (c) 2010-2013 Engine Yard, Inc.
258
+
269
259
  Warbler (c) 2007-2009 Sun Microsystems, Inc.
data/Rakefile CHANGED
@@ -7,9 +7,10 @@
7
7
 
8
8
  begin
9
9
  require 'bundler/setup'
10
- rescue LoadError
11
- puts $!
12
- puts "Please install Bundler and run 'bundle install' to ensure you have all dependencies"
10
+ rescue LoadError => e
11
+ require('rubygems') && retry
12
+ puts "Please `gem install bundler' and run `bundle install' to ensure you have all dependencies"
13
+ raise e
13
14
  end
14
15
 
15
16
  require 'bundler/gem_helper'
data/ext/JarMain.java CHANGED
@@ -6,143 +6,252 @@
6
6
  */
7
7
 
8
8
  import java.io.File;
9
+ import java.io.IOException;
9
10
  import java.io.FileOutputStream;
10
11
  import java.io.InputStream;
12
+ import java.lang.reflect.InvocationTargetException;
11
13
  import java.lang.reflect.Method;
14
+ import java.net.URI;
15
+ import java.net.URISyntaxException;
12
16
  import java.net.URL;
13
17
  import java.net.URLClassLoader;
14
18
  import java.util.ArrayList;
15
19
  import java.util.Arrays;
16
20
  import java.util.Enumeration;
21
+ import java.util.HashMap;
17
22
  import java.util.List;
23
+ import java.util.Map;
18
24
  import java.util.jar.JarEntry;
19
25
  import java.util.jar.JarFile;
20
26
 
21
27
  public class JarMain implements Runnable {
22
- public static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
28
+
29
+ static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
23
30
 
24
- private String[] args;
25
- private String path, jarfile;
26
- private boolean debug;
27
- private File extractRoot;
31
+ final boolean debug = isDebug();
32
+
33
+ protected final String[] args;
34
+ protected final String archive;
35
+ private final String path;
36
+
37
+ protected File extractRoot;
28
38
 
29
- public JarMain(String[] args) throws Exception {
39
+ JarMain(String[] args) {
30
40
  this.args = args;
31
41
  URL mainClass = getClass().getResource(MAIN);
32
- this.path = mainClass.toURI().getSchemeSpecificPart();
33
- this.jarfile = this.path.replace("!" + MAIN, "").replace("file:", "");
34
- this.debug = isDebug();
35
- this.extractRoot = File.createTempFile("jruby", "extract");
36
- this.extractRoot.delete();
37
- this.extractRoot.mkdirs();
42
+ try {
43
+ this.path = mainClass.toURI().getSchemeSpecificPart();
44
+ }
45
+ catch (URISyntaxException e) {
46
+ throw new RuntimeException(e);
47
+ }
48
+ archive = this.path.replace("!" + MAIN, "").replace("file:", "");
49
+
38
50
  Runtime.getRuntime().addShutdownHook(new Thread(this));
39
51
  }
52
+
53
+ protected URL[] extractArchive() throws Exception {
54
+ final JarFile jarFile = new JarFile(archive);
55
+ try {
56
+ Map<String, JarEntry> jarNames = new HashMap<String, JarEntry>();
57
+ for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
58
+ JarEntry entry = e.nextElement();
59
+ String extractPath = getExtractEntryPath(entry);
60
+ if ( extractPath != null ) jarNames.put(extractPath, entry);
61
+ }
62
+
63
+ extractRoot = File.createTempFile("jruby", "extract");
64
+ extractRoot.delete(); extractRoot.mkdirs();
40
65
 
41
- private URL[] extractJRuby() throws Exception {
42
- JarFile jf = new JarFile(this.jarfile);
43
- List<String> jarNames = new ArrayList<String>();
44
- for (Enumeration<JarEntry> eje = jf.entries(); eje.hasMoreElements(); ) {
45
- String name = eje.nextElement().getName();
46
- if (name.startsWith("META-INF/lib") && name.endsWith(".jar")) {
47
- jarNames.add("/" + name);
66
+ final List<URL> urls = new ArrayList<URL>();
67
+ for (Map.Entry<String, JarEntry> e : jarNames.entrySet()) {
68
+ URL entryURL = extractEntry(e.getValue(), e.getKey());
69
+ if (entryURL != null) urls.add( entryURL );
48
70
  }
71
+ return (URL[]) urls.toArray(new URL[urls.size()]);
49
72
  }
50
-
51
- List<URL> urls = new ArrayList<URL>();
52
- for (String name : jarNames) {
53
- urls.add(extractJar(name));
73
+ finally {
74
+ jarFile.close();
54
75
  }
55
-
56
- return (URL[]) urls.toArray(new URL[urls.size()]);
57
76
  }
58
77
 
59
- private URL extractJar(String jarpath) throws Exception {
60
- InputStream jarStream = new URL("jar:" + path.replace(MAIN, jarpath)).openStream();
61
- String jarname = jarpath.substring(jarpath.lastIndexOf("/") + 1, jarpath.lastIndexOf("."));
62
- File jarFile = new File(extractRoot, jarname + ".jar");
63
- jarFile.deleteOnExit();
64
- FileOutputStream outStream = new FileOutputStream(jarFile);
78
+ protected String getExtractEntryPath(final JarEntry entry) {
79
+ final String name = entry.getName();
80
+ if ( name.startsWith("META-INF/lib") && name.endsWith(".jar") ) {
81
+ return name.substring(name.lastIndexOf("/") + 1);
82
+ }
83
+ return null; // do not extract entry
84
+ }
85
+
86
+ protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
87
+ final File file = new File(extractRoot, path);
88
+ if ( entry.isDirectory() ) {
89
+ file.mkdirs();
90
+ return null;
91
+ }
92
+ final String entryPath = entryPath(entry.getName());
93
+ final InputStream entryStream;
94
+ try {
95
+ entryStream = new URI("jar", entryPath, null).toURL().openStream();
96
+ }
97
+ catch (IllegalArgumentException e) {
98
+ // TODO gems '%' file name "encoding" ?!
99
+ debug("failed to open jar:" + entryPath + " skipping entry: " + entry.getName(), e);
100
+ return null;
101
+ }
102
+ final File parent = file.getParentFile();
103
+ if ( parent != null ) parent.mkdirs();
104
+ FileOutputStream outStream = new FileOutputStream(file);
105
+ final byte[] buf = new byte[65536];
65
106
  try {
66
- byte[] buf = new byte[65536];
67
107
  int bytesRead = 0;
68
- while ((bytesRead = jarStream.read(buf)) != -1) {
108
+ while ((bytesRead = entryStream.read(buf)) != -1) {
69
109
  outStream.write(buf, 0, bytesRead);
70
110
  }
71
- } finally {
72
- jarStream.close();
111
+ }
112
+ finally {
113
+ entryStream.close();
73
114
  outStream.close();
115
+ file.deleteOnExit();
74
116
  }
75
- debug(jarname + ".jar extracted to " + jarFile.getPath());
76
- return jarFile.toURI().toURL();
117
+ if (false) debug(entry.getName() + " extracted to " + file.getPath());
118
+ return file.toURI().toURL();
119
+ }
120
+
121
+ protected String entryPath(String name) {
122
+ if ( ! name.startsWith("/") ) name = "/" + name;
123
+ return path.replace(MAIN, name);
77
124
  }
78
125
 
79
- private int launchJRuby(URL[] jars) throws Exception {
126
+ protected Object newScriptingContainer(final URL[] jars) throws Exception {
80
127
  System.setProperty("org.jruby.embed.class.path", "");
81
- URLClassLoader loader = new URLClassLoader(jars);
128
+ ClassLoader loader = new URLClassLoader(jars);
82
129
  Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, loader);
83
130
  Object scriptingContainer = scriptingContainerClass.newInstance();
131
+ debug("scripting container class loader urls: " + Arrays.toString(jars));
132
+ invokeMethod(scriptingContainer, "setArgv", (Object) args);
133
+ invokeMethod(scriptingContainer, "setClassLoader", new Class[] { ClassLoader.class }, loader);
134
+ return scriptingContainer;
135
+ }
136
+
137
+ protected int launchJRuby(final URL[] jars) throws Exception {
138
+ final Object scriptingContainer = newScriptingContainer(jars);
139
+ debug("invoking " + archive + " with: " + Arrays.deepToString(args));
140
+ Object outcome = invokeMethod(scriptingContainer, "runScriptlet", launchScript());
141
+ return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
142
+ }
84
143
 
85
- Method argv = scriptingContainerClass.getDeclaredMethod("setArgv", new Class[] {String[].class});
86
- argv.invoke(scriptingContainer, new Object[] {args});
87
- Method setClassLoader = scriptingContainerClass.getDeclaredMethod("setClassLoader", new Class[] {ClassLoader.class});
88
- setClassLoader.invoke(scriptingContainer, new Object[] {loader});
89
- debug("invoking " + jarfile + " with: " + Arrays.deepToString(args));
90
-
91
- Method runScriptlet = scriptingContainerClass.getDeclaredMethod("runScriptlet", new Class[] {String.class});
92
- return ((Number) runScriptlet.invoke(scriptingContainer, new Object[] {
93
- "begin\n" +
94
- "require 'META-INF/init.rb'\n" +
95
- "require 'META-INF/main.rb'\n" +
96
- "0\n" +
97
- "rescue SystemExit => e\n" +
98
- "e.status\n" +
99
- "end"
100
- })).intValue();
144
+ protected String launchScript() {
145
+ return
146
+ "begin\n" +
147
+ " require 'META-INF/init.rb'\n" +
148
+ " require 'META-INF/main.rb'\n" +
149
+ " 0\n" +
150
+ "rescue SystemExit => e\n" +
151
+ " e.status\n" +
152
+ "end";
153
+ }
154
+
155
+ protected int start() throws Exception {
156
+ final URL[] jars = extractArchive();
157
+ return launchJRuby(jars);
101
158
  }
102
159
 
103
- private int start() throws Exception {
104
- URL[] u = extractJRuby();
105
- return launchJRuby(u);
160
+ protected void debug(String msg) {
161
+ debug(msg, null);
106
162
  }
107
163
 
108
- private void debug(String msg) {
109
- if (debug) {
110
- System.out.println(msg);
164
+ protected void debug(String msg, Throwable t) {
165
+ if (debug) System.out.println(msg);
166
+ if (debug && t != null) t.printStackTrace(System.out);
167
+ }
168
+
169
+ protected void delete(File f) {
170
+ try {
171
+ if (f.isDirectory() && !isSymlink(f)) {
172
+ File[] children = f.listFiles();
173
+ for (int i = 0; i < children.length; i++) {
174
+ delete(children[i]);
175
+ }
176
+ }
177
+ f.delete();
178
+ } catch (IOException e) {
179
+ System.err.println("error: " + e.toString());
111
180
  }
112
181
  }
113
182
 
114
- private void delete(File f) {
115
- if (f.isDirectory()) {
116
- File[] children = f.listFiles();
117
- for (int i = 0; i < children.length; i++) {
118
- delete(children[i]);
119
- }
120
- }
121
- f.delete();
183
+ protected boolean isSymlink(File file) throws IOException {
184
+ if (file == null)
185
+ throw new NullPointerException("File must not be null");
186
+ File canon;
187
+ if (file.getParent() == null) {
188
+ canon = file;
189
+ } else {
190
+ File canonDir = file.getParentFile().getCanonicalFile();
191
+ canon = new File(canonDir, file.getName());
192
+ }
193
+ return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
122
194
  }
123
195
 
124
196
  public void run() {
125
- delete(extractRoot);
197
+ if ( extractRoot != null ) delete(extractRoot);
126
198
  }
127
199
 
128
200
  public static void main(String[] args) {
201
+ doStart(new JarMain(args));
202
+ }
203
+
204
+ protected static void doStart(final JarMain main) {
129
205
  try {
130
- int exit = new JarMain(args).start();
131
- System.exit(exit);
206
+ int exit = main.start();
207
+ if(isSystemExitEnabled()) System.exit(exit);
132
208
  } catch (Exception e) {
209
+ System.err.println("error: " + e.toString());
133
210
  Throwable t = e;
134
211
  while (t.getCause() != null && t.getCause() != t) {
135
212
  t = t.getCause();
136
213
  }
137
-
138
214
  if (isDebug()) {
139
215
  t.printStackTrace();
140
216
  }
141
217
  System.exit(1);
142
218
  }
143
219
  }
220
+
221
+ protected static Object invokeMethod(final Object self, final String name, final Object... args)
222
+ throws NoSuchMethodException, IllegalAccessException, Exception {
223
+
224
+ final Class[] signature = new Class[args.length];
225
+ for ( int i = 0; i < args.length; i++ ) signature[i] = args[i].getClass();
226
+ return invokeMethod(self, name, signature, args);
227
+ }
144
228
 
145
- private static boolean isDebug() {
146
- return System.getProperty("warbler.debug") != null;
229
+ protected static Object invokeMethod(final Object self, final String name, final Class[] signature, final Object... args)
230
+ throws NoSuchMethodException, IllegalAccessException, Exception {
231
+ Method method = self.getClass().getDeclaredMethod(name, signature);
232
+ try {
233
+ return method.invoke(self, args);
234
+ }
235
+ catch (InvocationTargetException e) {
236
+ Throwable target = e.getTargetException();
237
+ if (target instanceof Exception) {
238
+ throw (Exception) target;
239
+ }
240
+ throw e;
241
+ }
147
242
  }
243
+
244
+ static boolean isDebug() {
245
+ return Boolean.getBoolean("warbler.debug");
246
+ }
247
+
248
+ /**
249
+ * if warbler.skip_system_exit system property is defined, we will not
250
+ * call System.exit in the normal flow. System.exit can cause problems
251
+ * for wrappers like procrun
252
+ */
253
+ private static boolean isSystemExitEnabled(){
254
+ return System.getProperty("warbler.skip_system_exit") == null; //omission enables System.exit use
255
+ }
256
+
148
257
  }