require "uri" require "net/http" require "core/project" require "java/artifact" require "java/java" require "thread" module Buildr # Provides a collection of tasks and methods for using Jetty, specifically as a server # for testing your application. # # Build files should always start Jetty by invoking the #use task, typically as # a prerequisite. This task will start Jetty once during the build, and shut it down # when the build completes. # # If you want to keep Jetty running across builds, and look at error messages, you can # start Jetty in a separate console with: # rake jetty:start # To stop this instance of Jetty, simply kill the process (Ctrl-C) or run: # rake jetty:stop # # If you start Jetty separately from the build, the #use task will connect to that # existing server. Since you are using Jetty across several builds, you will want to # cleanup any mess created by each build. You can use the #setup and #teardown tasks, # which are called when Jetty is first used in the build, and when the build ends. class Jetty class << self # :call-seq: # instance() => Jetty # # Returns an instance of Jetty. def instance() @instance ||= Jetty.new end end # Artifacts required to run Jetty. REQUIRES = [ "org.mortbay.jetty:jetty:jar:6.1.1", "org.mortbay.jetty:jetty-util:jar:6.1.1", "org.mortbay.jetty:servlet-api-2.5:jar:6.1.1", "log4j:log4j:jar:1.2.13", "commons-logging:commons-logging:jar:1.1" ] Java.rjb.onload do Java.rjb.classpath << REQUIRES Java.rjb.classpath << File.join(__DIR__, "jetty") end # Default URL for Jetty. URL = "http://localhost:8080" def initialize() #:nodoc: @url = URL namespace "jetty" do @setup = task("setup") @teardown = task("teardown") @use = task("use") { fire } end end # The URL for the Jetty server. Leave as is if you want to use the default server # (http://localhost:8080). attr_accessor :url # :call-seq: # start(pipe?) # # Starts Jetty. This method does not return, it keeps the thread running until # Jetty is stopped. If you want to run Jetty parallel with other tasks in the build, # invoke the #use task instead. def start(sync = nil) begin Java.rjb do port = URI.parse(url).port puts "Starting Jetty at http://localhost:#{port}" if verbose jetty = Rjb::import("JettyWrapper").new(port) sync << "Started" if sync sleep # Forever end rescue Interrupt # Stopped from console rescue Exception=>error puts "#{error.class}: #{error.message}" end exit! # No at_exit end # :call-seq: # stop() # # Stops Jetty. Stops a server running in a separate process. def stop() uri = URI.parse(url) begin Net::HTTP.start(uri.host, uri.port) do |http| http.request_post "/buildr/stop", "" end rescue Errno::ECONNREFUSED # Expected if Jetty server not running. rescue EOFError # We get EOFError because Jetty is brutally killed. end puts "Jetty server stopped" end # :call-seq: # running?() => boolean # # Returns true if it finds a running Jetty server that supports the Buildr # requests for deploying, stopping, etc. def running?() uri = URI.parse(url) begin Net::HTTP.start(uri.host, uri.port) do |http| response = http.request_get("/buildr/") response.is_a?(Net::HTTPSuccess) && response.body =~ /Alive/ end rescue Errno::ECONNREFUSED, Errno::EBADF false end end # :call-seq: # deploy(url, webapp) => path # # Deploy a WAR in the specified URL. def deploy(url, webapp) use.invoke uri = URI.parse(url) Net::HTTP.start(uri.host, uri.port) do |http| response = http.request_post("/buildr/deploy", "webapp=#{webapp}&path=#{uri.path}") if Net::HTTPOK === response && response.body =~ /Deployed/ path = response.body.split[1] puts "Deployed #{webapp}, context path #{uri.path}" if Rake.application.options.trace path else fail "Deployment failed: #{response}" end end end # :call-seq: # undeploy(url) => boolean # # Undeploys a WAR from the specified URL. def undeploy(url) use.invoke uri = URI.parse(url) Net::HTTP.start(uri.host, uri.port) do |http| response = http.request_post("/buildr/undeploy", "path=#{uri.path}") if Net::HTTPOK === response && response.body =~ /Undeployed/ true else fail "Deployment failed: #{response}" end end end # :call-seq: # setup(*prereqs) => task # setup(*prereqs) { |task| .. } => task # # This task executes when Jetty is first used in the build. You can use it to # deploy artifacts into Jetty. def setup(*prereqs, &block) @setup.enhance prereqs, &block end # :call-seq: # teardown(*prereqs) => task # teardown(*prereqs) { |task| .. } => task # # This task executes when the build is done. You can use it to undeploy artifacts # previously deployed into Jetty. def teardown(*prereqs, &block) @teardown.enhance prereqs, &block end # :call-seq: # use(*prereqs) => task # use(*prereqs) { |task| .. } => task # # If you intend to use Jetty, invoke this task. It will start a new instance of # Jetty and close it when the build is done. However, if you already have a server # running in the background (e.g. jetty:start), it will use that server and will # not close it down. def use(*prereqs, &block) @use.enhance prereqs, &block end protected # If you want to start Jetty inside the build, call this method instead of #start. # It will spawn a separate process that will run Jetty, and will stop Jetty when # the build ends. However, if you already started Jetty from the console (with # take jetty:start), it will use the existing instance without shutting it down. def fire() unless running? sync = Queue.new Thread.new { start sync } # Wait for Jetty to fire up before doing anything else. sync.pop == "Started" or fail "Jetty not started" puts "Jetty started" if verbose at_exit { stop } end @setup.invoke at_exit { @teardown.invoke } end end namespace "jetty" do desc "Start an instance of Jetty running in the background" task("start") { Jetty.instance.start } desc "Stop an instance of Jetty running in the background" task("stop") { Jetty.instance.stop } end class Project # :call-seq: # jetty() => Jetty # # Returns a Jetty object. You can use this to discover the Jetty#use task, # configure the Jetty#setup and Jetty#teardown tasks, deploy and undeploy to Jetty. def jetty() @jetty ||= Jetty.instance end end end