Recently Adam, Steve (the Chief) and myself have been working on a new service that involves a brand new web-application. I won’t bore you with details of what it does because it won’t be publicly facing. Regardless, we had to consider deployment options and after a few false starts we decided to build an “executable WAR”. Basically a war file that you can start up by calling “java -jar mywebapp.war”.

Why?
First we liked the idea of having a fully contained application in a neat and tidy package. Second, this isn’t a case where we can build the war and let our operations team pick the container. Our application requires a servlet 3.0 container so our options were limited. And finally, we just wanted to learn how to do it

How?
Our first naive thought was that if a war file has a manifest with a Main-Class declaration that everything would just work. We were very, very wrong. Since war files keep their classes in WEB-INF/classes and not in the root of the jar this failed. Next we put our main class in the root of the jar, it started but our app server classes couldn’t be found because we put them in WEB-INF/lib, *sigh*. Then it all started coming together, any dependency that we typically consider “provided” will get un-jarred along with our main class into the root of the jar, then the rest will be the typical war format. It looks like this:

We did this all in buildr by adding our own packager and calling it :executable_war.

module Rally
module Packaging
module ExecutableWar
attr_accessor :main_class, :libs, :provided, :classes

def initialize(*args) #:nodoc:
super
enhance { |jar|
libs.each { |lib| lib.invoke if lib.respond_to?(:invoke) }
classes.each { |dir| jar.include dir, :as => 'WEB-INF/classes' }

classes.each do |dir|
class_dir = dir.to_s
class_dir_classes = "#{class_dir}/**/*.class"
main_class_path = main_class.gsub("\.", "/")

Dir.glob(class_dir_classes) do |file|
jar.path(File.dirname(file.sub(class_dir, ""))).include(file) if file.include?(main_class_path)
end
end

provided_target_dir = "target/provided"
mkdir_p(provided_target_dir)
cd(provided_target_dir) do
provided.each do |dependency|
jar -xf #{Buildr.artifact(dependency)}
end
end
jar.path("/").include("#{provided_target_dir}/*")
jar.path('WEB-INF/lib').include Buildr.artifacts(libs)
}
end

def main_class=(main_class)
@main_class = main_class
end

def version=(version)
@version = version
end

private
if self.manifest
self.manifest = self.manifest.merge(options)
else
self.manifest = options
end
end
end

def package_as_executable_war_spec(spec) #:nodoc:
spec.merge(:type=>:jar)
end

def package_as_executable_war(file_name) #:nodoc:
war.with path_to(:source, :main, :webapp)
war.with :classes => [compile.target, resources.target]
end
end
end
end
end

class Buildr::Project
include Rally::Packaging::ExecutableWar
end

And here is how we use it from our buildfile, where PROVIDED represents the buildr dependencies on things like Jetty and the Servlet API

package(:executable_war).with(
:libs => compile.dependencies - PROVIDED,
:main_class => "com.rallydev.server.jetty.EmbeddedJetty",
:provided => PROVIDED,
:version => "1.0")

And to start the server, as alluded to above chose Jetty 8, here is our code. Note the DEV_MODE flag so we can still do in place development when not deploying the jar file.

package com.rallydev.server.jetty;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class EmbeddedJetty {

public static void main(String[] args) throws Exception {

String warPath = Boolean.getBoolean("DEV_MODE") ?
"src/main/webapp" :
EmbeddedJetty.class.getProtectionDomain().getCodeSource().getLocation().getPath();

WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setWar(warPath);
server.setHandler(context);

server.start();
server.join();
}

}