2009-06-26 03:26:06 +02:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2008 Mark Harrah
|
|
|
|
|
*/
|
|
|
|
|
package sbt
|
|
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.net.{URL, URLClassLoader}
|
|
|
|
|
import scala.xml.NodeSeq
|
|
|
|
|
|
2009-08-02 03:26:18 +02:00
|
|
|
object JettyRunner
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
|
|
|
|
val DefaultPort = 8080
|
2009-08-02 03:26:18 +02:00
|
|
|
val DefaultScanInterval = 3
|
|
|
|
|
}
|
|
|
|
|
class JettyRunner(configuration: JettyConfiguration) extends ExitHook
|
|
|
|
|
{
|
2009-06-26 03:26:06 +02:00
|
|
|
ExitHooks.register(this)
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
def name = "jetty-shutdown"
|
|
|
|
|
def runBeforeExiting() { stop() }
|
|
|
|
|
private var running: Option[Stoppable] = None
|
|
|
|
|
private def started(s: Stoppable) { running = Some(s) }
|
|
|
|
|
def stop()
|
|
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
running.foreach(_.stop())
|
|
|
|
|
running = None
|
|
|
|
|
}
|
|
|
|
|
def apply(): Option[String] =
|
|
|
|
|
{
|
|
|
|
|
import configuration._
|
|
|
|
|
def runJetty() =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
val baseLoader = this.getClass.getClassLoader
|
|
|
|
|
val classpathURLs = jettyClasspath.get.map(_.asURL).toSeq
|
|
|
|
|
val loader: ClassLoader = new java.net.URLClassLoader(classpathURLs.toArray, baseLoader)
|
2009-12-18 15:47:02 +01:00
|
|
|
val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.classLocation[Stoppable].toURI.toURL), loader, baseLoader)
|
2009-08-02 03:26:18 +02:00
|
|
|
val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun]
|
|
|
|
|
runner(configuration)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-08-02 03:26:18 +02:00
|
|
|
if(running.isDefined)
|
|
|
|
|
Some("This instance of Jetty is already running.")
|
|
|
|
|
else
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
try
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
started(runJetty())
|
|
|
|
|
None
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2009-08-02 03:26:18 +02:00
|
|
|
catch
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
case e: NoClassDefFoundError => runError(e, "Jetty and its dependencies must be on the " + classpathName + " classpath: ", log)
|
|
|
|
|
case e => runError(e, "Error running Jetty: ", log)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
2009-08-02 03:26:18 +02:00
|
|
|
}
|
2009-06-26 03:26:06 +02:00
|
|
|
private val implClassName = "sbt.LazyJettyRun"
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
private def runError(e: Throwable, messageBase: String, log: Logger) =
|
|
|
|
|
{
|
|
|
|
|
log.trace(e)
|
|
|
|
|
Some(messageBase + e.toString)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private trait Stoppable
|
|
|
|
|
{
|
|
|
|
|
def stop(): Unit
|
|
|
|
|
}
|
|
|
|
|
private trait JettyRun
|
|
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
def apply(configuration: JettyConfiguration): Stoppable
|
|
|
|
|
}
|
|
|
|
|
sealed trait JettyConfiguration extends NotNull
|
|
|
|
|
{
|
|
|
|
|
def war: Path
|
|
|
|
|
def scanDirectories: Seq[File]
|
|
|
|
|
def scanInterval: Int
|
|
|
|
|
/** The classpath to get Jetty from. */
|
|
|
|
|
def jettyClasspath: PathFinder
|
|
|
|
|
def classpathName: String
|
|
|
|
|
def log: Logger
|
|
|
|
|
}
|
|
|
|
|
trait DefaultJettyConfiguration extends JettyConfiguration
|
|
|
|
|
{
|
|
|
|
|
def contextPath: String
|
|
|
|
|
def port: Int
|
2009-10-10 01:31:55 +02:00
|
|
|
/** The classpath containing the classes, jars, and resources for the web application. */
|
|
|
|
|
def classpath: PathFinder
|
|
|
|
|
def parentLoader: ClassLoader
|
2009-08-02 03:26:18 +02:00
|
|
|
}
|
|
|
|
|
abstract class CustomJettyConfiguration extends JettyConfiguration
|
|
|
|
|
{
|
|
|
|
|
def jettyConfigurationFiles: Seq[File] = Nil
|
|
|
|
|
def jettyConfigurationXML: NodeSeq = NodeSeq.Empty
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This class starts Jetty.
|
|
|
|
|
* NOTE: DO NOT actively use this class. You will see NoClassDefFoundErrors if you fail
|
|
|
|
|
* to do so.Only use its name in JettyRun for reflective loading. This allows using
|
|
|
|
|
* the Jetty libraries provided on the project classpath instead of requiring them to be
|
|
|
|
|
* available on sbt's classpath at startup.
|
|
|
|
|
*/
|
|
|
|
|
private object LazyJettyRun extends JettyRun
|
|
|
|
|
{
|
|
|
|
|
import org.mortbay.jetty.{Handler, Server}
|
|
|
|
|
import org.mortbay.jetty.nio.SelectChannelConnector
|
|
|
|
|
import org.mortbay.jetty.webapp.WebAppContext
|
|
|
|
|
import org.mortbay.log.Log
|
|
|
|
|
import org.mortbay.util.Scanner
|
|
|
|
|
import org.mortbay.xml.XmlConfiguration
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
import java.lang.ref.{Reference, WeakReference}
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
val DefaultMaxIdleTime = 30000
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-08-02 03:26:18 +02:00
|
|
|
def apply(configuration: JettyConfiguration): Stoppable =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
|
|
|
|
val oldLog = Log.getLog
|
2009-08-02 03:26:18 +02:00
|
|
|
Log.setLog(new JettyLogger(configuration.log))
|
2009-06-26 03:26:06 +02:00
|
|
|
val server = new Server
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
val listener =
|
2009-08-02 03:26:18 +02:00
|
|
|
configuration match
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
case c: DefaultJettyConfiguration =>
|
|
|
|
|
import c._
|
|
|
|
|
configureDefaultConnector(server, port)
|
|
|
|
|
def classpathURLs = classpath.get.map(_.asURL).toSeq
|
2009-10-10 01:31:55 +02:00
|
|
|
def createLoader = new URLClassLoader(classpathURLs.toArray, parentLoader)
|
2009-08-02 03:26:18 +02:00
|
|
|
val webapp = new WebAppContext(war.absolutePath, contextPath)
|
|
|
|
|
webapp.setClassLoader(createLoader)
|
|
|
|
|
server.setHandler(webapp)
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-08-02 03:26:18 +02:00
|
|
|
Some(new Scanner.BulkListener {
|
|
|
|
|
def filesChanged(files: java.util.List[_]) {
|
|
|
|
|
reload(server, webapp.setClassLoader(createLoader), log)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
case c: CustomJettyConfiguration =>
|
|
|
|
|
for(x <- c.jettyConfigurationXML)
|
|
|
|
|
(new XmlConfiguration(x.toString)).configure(server)
|
|
|
|
|
for(file <- c.jettyConfigurationFiles)
|
|
|
|
|
(new XmlConfiguration(file.toURI.toURL)).configure(server)
|
|
|
|
|
None
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
def configureScanner() =
|
|
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
val scanDirectories = configuration.scanDirectories
|
2009-06-26 03:26:06 +02:00
|
|
|
if(listener.isEmpty || scanDirectories.isEmpty)
|
|
|
|
|
None
|
|
|
|
|
else
|
|
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", "))
|
2009-06-26 03:26:06 +02:00
|
|
|
val scanner = new Scanner
|
|
|
|
|
val list = new java.util.ArrayList[File]
|
|
|
|
|
scanDirectories.foreach(x => list.add(x))
|
|
|
|
|
scanner.setScanDirs(list)
|
|
|
|
|
scanner.setRecursive(true)
|
2009-08-02 03:26:18 +02:00
|
|
|
scanner.setScanInterval(configuration.scanInterval)
|
2009-06-26 03:26:06 +02:00
|
|
|
scanner.setReportExistingFilesOnStartup(false)
|
|
|
|
|
scanner.addListener(listener.get)
|
|
|
|
|
scanner.start()
|
|
|
|
|
Some(new WeakReference(scanner))
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
server.start()
|
|
|
|
|
new StopServer(new WeakReference(server), configureScanner(), oldLog)
|
|
|
|
|
}
|
|
|
|
|
catch { case e => server.stop(); throw e }
|
|
|
|
|
}
|
|
|
|
|
private def configureDefaultConnector(server: Server, port: Int)
|
|
|
|
|
{
|
|
|
|
|
val defaultConnector = new SelectChannelConnector
|
|
|
|
|
defaultConnector.setPort(port)
|
|
|
|
|
defaultConnector.setMaxIdleTime(DefaultMaxIdleTime)
|
|
|
|
|
server.addConnector(defaultConnector)
|
|
|
|
|
}
|
|
|
|
|
private class StopServer(serverReference: Reference[Server], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: org.mortbay.log.Logger) extends Stoppable
|
|
|
|
|
{
|
|
|
|
|
def stop()
|
|
|
|
|
{
|
|
|
|
|
val server = serverReference.get
|
|
|
|
|
if(server != null)
|
|
|
|
|
server.stop()
|
|
|
|
|
for(scannerReference <- scannerReferenceOpt)
|
|
|
|
|
{
|
|
|
|
|
val scanner = scannerReference.get
|
|
|
|
|
if(scanner != null)
|
|
|
|
|
scanner.stop()
|
|
|
|
|
}
|
|
|
|
|
Log.setLog(oldLog)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private def reload(server: Server, reconfigure: => Unit, log: Logger)
|
|
|
|
|
{
|
2009-08-02 03:26:18 +02:00
|
|
|
log.info("Reloading web application...")
|
|
|
|
|
val handlers = wrapNull(server.getHandlers, server.getHandler)
|
|
|
|
|
log.debug("Stopping handlers: " + handlers.mkString(", "))
|
|
|
|
|
handlers.foreach(_.stop)
|
|
|
|
|
log.debug("Reconfiguring...")
|
|
|
|
|
reconfigure
|
|
|
|
|
log.debug("Restarting handlers: " + handlers.mkString(", "))
|
|
|
|
|
handlers.foreach(_.start)
|
|
|
|
|
log.info("Reload complete.")
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
private def wrapNull(a: Array[Handler], b: Handler) =
|
|
|
|
|
(a, b) match
|
|
|
|
|
{
|
|
|
|
|
case (null, null) => Nil
|
|
|
|
|
case (null, notB) => notB :: Nil
|
|
|
|
|
case (notA, null) => notA.toList
|
|
|
|
|
case (notA, notB) => notB :: notA.toList
|
|
|
|
|
}
|
|
|
|
|
private class JettyLogger(delegate: Logger) extends org.mortbay.log.Logger
|
|
|
|
|
{
|
|
|
|
|
def isDebugEnabled = delegate.atLevel(Level.Debug)
|
|
|
|
|
def setDebugEnabled(enabled: Boolean) = delegate.setLevel(if(enabled) Level.Debug else Level.Info)
|
2009-10-10 01:31:55 +02:00
|
|
|
|
2009-06-26 03:26:06 +02:00
|
|
|
def getLogger(name: String) = this
|
|
|
|
|
def info(msg: String, arg0: AnyRef, arg1: AnyRef) { delegate.info(format(msg, arg0, arg1)) }
|
|
|
|
|
def debug(msg: String, arg0: AnyRef, arg1: AnyRef) { delegate.debug(format(msg, arg0, arg1)) }
|
|
|
|
|
def warn(msg: String, arg0: AnyRef, arg1: AnyRef) { delegate.warn(format(msg, arg0, arg1)) }
|
|
|
|
|
def warn(msg: String, th: Throwable)
|
|
|
|
|
{
|
|
|
|
|
delegate.warn(msg)
|
|
|
|
|
delegate.trace(th)
|
|
|
|
|
}
|
|
|
|
|
def debug(msg: String, th: Throwable)
|
|
|
|
|
{
|
|
|
|
|
delegate.debug(msg)
|
|
|
|
|
delegate.trace(th)
|
|
|
|
|
}
|
|
|
|
|
private def format(msg: String, arg0: AnyRef, arg1: AnyRef) =
|
|
|
|
|
{
|
|
|
|
|
def toString(arg: AnyRef) = if(arg == null) "" else arg.toString
|
|
|
|
|
val pieces = msg.split("""\{\}""", 3)
|
|
|
|
|
if(pieces.length == 1)
|
|
|
|
|
pieces(0)
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
val base = pieces(0) + toString(arg0) + pieces(1)
|
|
|
|
|
if(pieces.length == 2)
|
|
|
|
|
base
|
|
|
|
|
else
|
|
|
|
|
base + toString(arg1) + pieces(2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|