mirror of https://github.com/sbt/sbt.git
Jetty 7 support (untested)
This commit is contained in:
parent
510a708a08
commit
aaea9c3407
|
|
@ -28,28 +28,13 @@ class SbtProject(info: ProjectInfo) extends DefaultProject(info) with test.SbtSc
|
|||
// Configure which versions of Scala to test against for those tests that do cross building
|
||||
override def scriptedCompatibility = sbt.test.CompatibilityLevel.Full
|
||||
|
||||
def scalaVersionString = ScalaVersion.current.getOrElse(scalaVersion.value)
|
||||
override def mainSources =
|
||||
{
|
||||
if(scalaVersionString == Version2_8_0)
|
||||
Path.lazyPathFinder( super.mainSources.get.filter( !_.asFile.getName.endsWith("TestFrameworkImpl.scala") ))
|
||||
else
|
||||
super.mainSources
|
||||
}
|
||||
|
||||
override def useDefaultConfigurations = false
|
||||
val default = Configurations.Default
|
||||
val optional = Configurations.Optional
|
||||
val provided = Configurations.Provided
|
||||
val testConf = Configurations.Test
|
||||
|
||||
/* Versions of Scala to cross-build against. */
|
||||
private val Version2_7_7 = "2.7.7"
|
||||
private val Version2_8_0 = "2.8.0-SNAPSHOT"
|
||||
// the list of all supported versions
|
||||
private def allVersions = Version2_7_7 :: Version2_8_0 :: Nil
|
||||
|
||||
override def crossScalaVersions = Set(Version2_7_7)
|
||||
override def crossScalaVersions = Set("2.7.7")
|
||||
|
||||
//testing
|
||||
val scalacheck = "org.scala-tools.testing" %% "scalacheck" % "1.6" % "test"
|
||||
|
|
@ -57,6 +42,9 @@ class SbtProject(info: ProjectInfo) extends DefaultProject(info) with test.SbtSc
|
|||
val ivy = "org.apache.ivy" % "ivy" % "2.1.0" intransitive()
|
||||
val jsch = "com.jcraft" % "jsch" % "0.1.31" intransitive()
|
||||
val jetty = "org.mortbay.jetty" % "jetty" % "6.1.14" % "optional"
|
||||
|
||||
val jetty7server = "org.eclipse.jetty" % "jetty-server" % "7.0.1.v20091125" % "optional"
|
||||
val jetty7webapp = "org.eclipse.jetty" % "jetty-webapp" % "7.0.1.v20091125" % "optional"
|
||||
|
||||
val testInterface = "org.scala-tools.testing" % "test-interface" % "0.4"
|
||||
|
||||
|
|
@ -64,8 +52,19 @@ class SbtProject(info: ProjectInfo) extends DefaultProject(info) with test.SbtSc
|
|||
val xsbti = "org.scala-tools.sbt" % "launcher-interface" % projectVersion.value.toString % "provided"
|
||||
val compiler = "org.scala-tools.sbt" %% "compile" % projectVersion.value.toString
|
||||
|
||||
override def libraryDependencies = super.libraryDependencies ++ getDependencies(scalaVersionString)
|
||||
|
||||
def getDependencies(scalaVersion: String) =
|
||||
if(scalaVersion == Version2_8_0) Seq("jline" % "jline" % "0.9.94" intransitive()) else Nil
|
||||
/* For generating JettyRun for Jetty 6 and 7. The only difference is the imports, but the file has to be compiled against each set of imports. */
|
||||
override def compileAction = super.compileAction dependsOn (generateJettyRun6, generateJettyRun7)
|
||||
def jettySrcDir = mainScalaSourcePath / "sbt" / "jetty"
|
||||
def jettyTemplate = jettySrcDir / "LazyJettyRun.scala.templ"
|
||||
|
||||
lazy val generateJettyRun6 = generateJettyRun(jettyTemplate, jettySrcDir / "LazyJettyRun6.scala", "6", jettySrcDir / "jetty6.imports")
|
||||
lazy val generateJettyRun7 = generateJettyRun(jettyTemplate, jettySrcDir / "LazyJettyRun7.scala", "7", jettySrcDir / "jetty7.imports")
|
||||
def generateJettyRun(in: Path, out: Path, version: String, importsPath: Path) =
|
||||
task
|
||||
{
|
||||
(for(template <- FileUtilities.readString(in asFile, log).right; imports <- FileUtilities.readString(importsPath asFile, log).right) yield
|
||||
FileUtilities.write(out asFile, processJettyTemplate(template, version, imports), log).toLeft(()) ).left.toOption
|
||||
}
|
||||
def processJettyTemplate(template: String, version: String, imports: String): String =
|
||||
template.replaceAll("""\Q${jetty.version}\E""", version).replaceAll("""\Q${jetty.imports}\E""", imports)
|
||||
}
|
||||
|
|
@ -41,8 +41,12 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook
|
|||
|
||||
val dual = new xsbt.DualLoader(baseLoader, notJettyFilter, x => true, jettyLoader, jettyFilter, x => false)
|
||||
|
||||
val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.classLocation[Stoppable].toURI.toURL), dual, baseLoader)
|
||||
val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun]
|
||||
def createRunner(implClassName: String) =
|
||||
{
|
||||
val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.classLocation[Stoppable].toURI.toURL), dual, baseLoader)
|
||||
ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun]
|
||||
}
|
||||
val runner = try { createRunner(implClassName6) } catch { case e: NoClassDefFoundError => createRunner(implClassName7) }
|
||||
runner(configuration, jettyLoader)
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +66,8 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook
|
|||
}
|
||||
}
|
||||
}
|
||||
private val implClassName = "sbt.LazyJettyRun"
|
||||
private val implClassName6 = "sbt.jetty.LazyJettyRun6"
|
||||
private val implClassName7 = "sbt.jetty.LazyJettyRun7"
|
||||
|
||||
private def runError(e: Throwable, messageBase: String, log: Logger) =
|
||||
{
|
||||
|
|
@ -105,165 +110,41 @@ abstract class CustomJettyConfiguration extends JettyConfiguration
|
|||
def jettyConfigurationXML: NodeSeq = NodeSeq.Empty
|
||||
}
|
||||
|
||||
/* 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
|
||||
private class JettyLoggerBase(delegate: Logger)
|
||||
{
|
||||
import org.mortbay.jetty.{Handler, Server}
|
||||
import org.mortbay.jetty.nio.SelectChannelConnector
|
||||
import org.mortbay.jetty.webapp.{WebAppClassLoader, WebAppContext}
|
||||
import org.mortbay.log.Log
|
||||
import org.mortbay.util.Scanner
|
||||
import org.mortbay.xml.XmlConfiguration
|
||||
def getName = "JettyLogger"
|
||||
def isDebugEnabled = delegate.atLevel(Level.Debug)
|
||||
def setDebugEnabled(enabled: Boolean) = delegate.setLevel(if(enabled) Level.Debug else Level.Info)
|
||||
|
||||
import java.lang.ref.{Reference, WeakReference}
|
||||
|
||||
val DefaultMaxIdleTime = 30000
|
||||
|
||||
def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable =
|
||||
def info(msg: String) { delegate.info(msg) }
|
||||
def debug(msg: String) { delegate.warn(msg) }
|
||||
def warn(msg: String) { delegate.warn(msg) }
|
||||
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)
|
||||
{
|
||||
val oldLog = Log.getLog
|
||||
Log.setLog(new JettyLogger(configuration.log))
|
||||
val server = new Server
|
||||
|
||||
def configureScanner(listener: Scanner.BulkListener, scanDirectories: Seq[File], scanInterval: Int) =
|
||||
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
|
||||
{
|
||||
if(scanDirectories.isEmpty)
|
||||
None
|
||||
val base = pieces(0) + toString(arg0) + pieces(1)
|
||||
if(pieces.length == 2)
|
||||
base
|
||||
else
|
||||
{
|
||||
configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", "))
|
||||
val scanner = new Scanner
|
||||
val list = new java.util.ArrayList[File]
|
||||
scanDirectories.foreach(x => list.add(x))
|
||||
scanner.setScanDirs(list)
|
||||
scanner.setRecursive(true)
|
||||
scanner.setScanInterval(scanInterval)
|
||||
scanner.setReportExistingFilesOnStartup(false)
|
||||
scanner.addListener(listener)
|
||||
scanner.start()
|
||||
Some(new WeakReference(scanner))
|
||||
}
|
||||
}
|
||||
|
||||
val (listener, scanner) =
|
||||
configuration match
|
||||
{
|
||||
case c: DefaultJettyConfiguration =>
|
||||
import c._
|
||||
configureDefaultConnector(server, port)
|
||||
def classpathURLs = classpath.get.map(_.asURL).toSeq
|
||||
val webapp = new WebAppContext(war.absolutePath, contextPath)
|
||||
|
||||
def createLoader =
|
||||
{
|
||||
class SbtWebAppLoader extends WebAppClassLoader(jettyLoader, webapp) { override def addURL(u: URL) = super.addURL(u) };
|
||||
val loader = new SbtWebAppLoader
|
||||
classpathURLs.foreach(loader.addURL)
|
||||
loader
|
||||
}
|
||||
def setLoader() = webapp.setClassLoader(createLoader)
|
||||
|
||||
setLoader()
|
||||
server.setHandler(webapp)
|
||||
|
||||
val listener = new Scanner.BulkListener with Reload {
|
||||
def reloadApp() = reload(server, setLoader(), log)
|
||||
def filesChanged(files: java.util.List[_]) { reloadApp() }
|
||||
}
|
||||
(Some(listener), configureScanner(listener, c.scanDirectories, c.scanInterval))
|
||||
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, None)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
server.start()
|
||||
new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), scanner, 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)
|
||||
}
|
||||
trait Reload { def reloadApp(): Unit }
|
||||
private class StopServer(serverReference: Reference[Server], reloadReference: Option[Reference[Reload]], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: org.mortbay.log.Logger) extends Stoppable
|
||||
{
|
||||
def reload(): Unit = on(reloadReference)(_.reloadApp())
|
||||
private def on[T](refOpt: Option[Reference[T]])(f: T => Unit): Unit = refOpt.foreach(ref => onReferenced(ref.get)(f))
|
||||
private def onReferenced[T](t: T)(f: T => Unit): Unit = if(t == null) () else f(t)
|
||||
def stop()
|
||||
{
|
||||
onReferenced(serverReference.get)(_.stop())
|
||||
on(scannerReferenceOpt)(_.stop())
|
||||
Log.setLog(oldLog)
|
||||
base + toString(arg1) + pieces(2)
|
||||
}
|
||||
}
|
||||
private def reload(server: Server, reconfigure: => Unit, log: Logger)
|
||||
{
|
||||
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.")
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package sbt.jetty
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/* 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${jetty.version} extends JettyRun
|
||||
{
|
||||
${jetty.imports}
|
||||
|
||||
import java.lang.ref.{Reference, WeakReference}
|
||||
|
||||
val DefaultMaxIdleTime = 30000
|
||||
|
||||
def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable =
|
||||
{
|
||||
val oldLog = Log.getLog
|
||||
Log.setLog(new JettyLogger(configuration.log))
|
||||
val server = new Server
|
||||
|
||||
def configureScanner(listener: Scanner.BulkListener, scanDirectories: Seq[File], scanInterval: Int) =
|
||||
{
|
||||
if(scanDirectories.isEmpty)
|
||||
None
|
||||
else
|
||||
{
|
||||
configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", "))
|
||||
val scanner = new Scanner
|
||||
val list = new java.util.ArrayList[File]
|
||||
scanDirectories.foreach(x => list.add(x))
|
||||
scanner.setScanDirs(list)
|
||||
scanner.setRecursive(true)
|
||||
scanner.setScanInterval(scanInterval)
|
||||
scanner.setReportExistingFilesOnStartup(false)
|
||||
scanner.addListener(listener)
|
||||
scanner.start()
|
||||
Some(new WeakReference(scanner))
|
||||
}
|
||||
}
|
||||
|
||||
val (listener, scanner) =
|
||||
configuration match
|
||||
{
|
||||
case c: DefaultJettyConfiguration =>
|
||||
import c._
|
||||
configureDefaultConnector(server, port)
|
||||
def classpathURLs = classpath.get.map(_.asURL).toSeq
|
||||
val webapp = new WebAppContext(war.absolutePath, contextPath)
|
||||
|
||||
def createLoader =
|
||||
{
|
||||
class SbtWebAppLoader extends WebAppClassLoader(jettyLoader, webapp) { override def addURL(u: URL) = super.addURL(u) };
|
||||
val loader = new SbtWebAppLoader
|
||||
classpathURLs.foreach(loader.addURL)
|
||||
loader
|
||||
}
|
||||
def setLoader() = webapp.setClassLoader(createLoader)
|
||||
|
||||
setLoader()
|
||||
server.setHandler(webapp)
|
||||
|
||||
val listener = new Scanner.BulkListener with Reload {
|
||||
def reloadApp() = reload(server, setLoader(), log)
|
||||
def filesChanged(files: java.util.List[_]) { reloadApp() }
|
||||
}
|
||||
(Some(listener), configureScanner(listener, c.scanDirectories, c.scanInterval))
|
||||
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, None)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
server.start()
|
||||
new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), scanner, 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)
|
||||
}
|
||||
trait Reload { def reloadApp(): Unit }
|
||||
private class StopServer(serverReference: Reference[Server], reloadReference: Option[Reference[Reload]], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: JLogger) extends Stoppable
|
||||
{
|
||||
def reload(): Unit = on(reloadReference)(_.reloadApp())
|
||||
private def on[T](refOpt: Option[Reference[T]])(f: T => Unit): Unit = refOpt.foreach(ref => onReferenced(ref.get)(f))
|
||||
private def onReferenced[T](t: T)(f: T => Unit): Unit = if(t == null) () else f(t)
|
||||
def stop()
|
||||
{
|
||||
onReferenced(serverReference.get)(_.stop())
|
||||
on(scannerReferenceOpt)(_.stop())
|
||||
Log.setLog(oldLog)
|
||||
}
|
||||
}
|
||||
private def reload(server: Server, reconfigure: => Unit, log: Logger)
|
||||
{
|
||||
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.")
|
||||
}
|
||||
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 JettyLoggerBase(delegate) with JLogger
|
||||
{
|
||||
def getLogger(name: String) = this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package sbt.jetty
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/* 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 LazyJettyRun6 extends JettyRun
|
||||
{
|
||||
|
||||
import org.mortbay.jetty.{Handler, Server}
|
||||
import org.mortbay.jetty.nio.SelectChannelConnector
|
||||
import org.mortbay.jetty.webapp.{WebAppClassLoader, WebAppContext}
|
||||
import org.mortbay.log.{Log, Logger => JLogger}
|
||||
import org.mortbay.util.Scanner
|
||||
import org.mortbay.xml.XmlConfiguration
|
||||
|
||||
import java.lang.ref.{Reference, WeakReference}
|
||||
|
||||
val DefaultMaxIdleTime = 30000
|
||||
|
||||
def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable =
|
||||
{
|
||||
val oldLog = Log.getLog
|
||||
Log.setLog(new JettyLogger(configuration.log))
|
||||
val server = new Server
|
||||
|
||||
def configureScanner(listener: Scanner.BulkListener, scanDirectories: Seq[File], scanInterval: Int) =
|
||||
{
|
||||
if(scanDirectories.isEmpty)
|
||||
None
|
||||
else
|
||||
{
|
||||
configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", "))
|
||||
val scanner = new Scanner
|
||||
val list = new java.util.ArrayList[File]
|
||||
scanDirectories.foreach(x => list.add(x))
|
||||
scanner.setScanDirs(list)
|
||||
scanner.setRecursive(true)
|
||||
scanner.setScanInterval(scanInterval)
|
||||
scanner.setReportExistingFilesOnStartup(false)
|
||||
scanner.addListener(listener)
|
||||
scanner.start()
|
||||
Some(new WeakReference(scanner))
|
||||
}
|
||||
}
|
||||
|
||||
val (listener, scanner) =
|
||||
configuration match
|
||||
{
|
||||
case c: DefaultJettyConfiguration =>
|
||||
import c._
|
||||
configureDefaultConnector(server, port)
|
||||
def classpathURLs = classpath.get.map(_.asURL).toSeq
|
||||
val webapp = new WebAppContext(war.absolutePath, contextPath)
|
||||
|
||||
def createLoader =
|
||||
{
|
||||
class SbtWebAppLoader extends WebAppClassLoader(jettyLoader, webapp) { override def addURL(u: URL) = super.addURL(u) };
|
||||
val loader = new SbtWebAppLoader
|
||||
classpathURLs.foreach(loader.addURL)
|
||||
loader
|
||||
}
|
||||
def setLoader() = webapp.setClassLoader(createLoader)
|
||||
|
||||
setLoader()
|
||||
server.setHandler(webapp)
|
||||
|
||||
val listener = new Scanner.BulkListener with Reload {
|
||||
def reloadApp() = reload(server, setLoader(), log)
|
||||
def filesChanged(files: java.util.List[_]) { reloadApp() }
|
||||
}
|
||||
(Some(listener), configureScanner(listener, c.scanDirectories, c.scanInterval))
|
||||
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, None)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
server.start()
|
||||
new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), scanner, 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)
|
||||
}
|
||||
trait Reload { def reloadApp(): Unit }
|
||||
private class StopServer(serverReference: Reference[Server], reloadReference: Option[Reference[Reload]], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: JLogger) extends Stoppable
|
||||
{
|
||||
def reload(): Unit = on(reloadReference)(_.reloadApp())
|
||||
private def on[T](refOpt: Option[Reference[T]])(f: T => Unit): Unit = refOpt.foreach(ref => onReferenced(ref.get)(f))
|
||||
private def onReferenced[T](t: T)(f: T => Unit): Unit = if(t == null) () else f(t)
|
||||
def stop()
|
||||
{
|
||||
onReferenced(serverReference.get)(_.stop())
|
||||
on(scannerReferenceOpt)(_.stop())
|
||||
Log.setLog(oldLog)
|
||||
}
|
||||
}
|
||||
private def reload(server: Server, reconfigure: => Unit, log: Logger)
|
||||
{
|
||||
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.")
|
||||
}
|
||||
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 JettyLoggerBase(delegate) with JLogger
|
||||
{
|
||||
def getLogger(name: String) = this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package sbt.jetty
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/* 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 LazyJettyRun7 extends JettyRun
|
||||
{
|
||||
|
||||
import org.eclipse.jetty.server.{Server, Handler}
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector
|
||||
import org.eclipse.jetty.webapp.{WebAppClassLoader, WebAppContext}
|
||||
import org.eclipse.jetty.util.log.{Log, Logger => JLogger}
|
||||
import org.eclipse.jetty.util.Scanner
|
||||
import org.eclipse.jetty.xml.XmlConfiguration
|
||||
|
||||
import java.lang.ref.{Reference, WeakReference}
|
||||
|
||||
val DefaultMaxIdleTime = 30000
|
||||
|
||||
def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable =
|
||||
{
|
||||
val oldLog = Log.getLog
|
||||
Log.setLog(new JettyLogger(configuration.log))
|
||||
val server = new Server
|
||||
|
||||
def configureScanner(listener: Scanner.BulkListener, scanDirectories: Seq[File], scanInterval: Int) =
|
||||
{
|
||||
if(scanDirectories.isEmpty)
|
||||
None
|
||||
else
|
||||
{
|
||||
configuration.log.debug("Scanning for changes to: " + scanDirectories.mkString(", "))
|
||||
val scanner = new Scanner
|
||||
val list = new java.util.ArrayList[File]
|
||||
scanDirectories.foreach(x => list.add(x))
|
||||
scanner.setScanDirs(list)
|
||||
scanner.setRecursive(true)
|
||||
scanner.setScanInterval(scanInterval)
|
||||
scanner.setReportExistingFilesOnStartup(false)
|
||||
scanner.addListener(listener)
|
||||
scanner.start()
|
||||
Some(new WeakReference(scanner))
|
||||
}
|
||||
}
|
||||
|
||||
val (listener, scanner) =
|
||||
configuration match
|
||||
{
|
||||
case c: DefaultJettyConfiguration =>
|
||||
import c._
|
||||
configureDefaultConnector(server, port)
|
||||
def classpathURLs = classpath.get.map(_.asURL).toSeq
|
||||
val webapp = new WebAppContext(war.absolutePath, contextPath)
|
||||
|
||||
def createLoader =
|
||||
{
|
||||
class SbtWebAppLoader extends WebAppClassLoader(jettyLoader, webapp) { override def addURL(u: URL) = super.addURL(u) };
|
||||
val loader = new SbtWebAppLoader
|
||||
classpathURLs.foreach(loader.addURL)
|
||||
loader
|
||||
}
|
||||
def setLoader() = webapp.setClassLoader(createLoader)
|
||||
|
||||
setLoader()
|
||||
server.setHandler(webapp)
|
||||
|
||||
val listener = new Scanner.BulkListener with Reload {
|
||||
def reloadApp() = reload(server, setLoader(), log)
|
||||
def filesChanged(files: java.util.List[_]) { reloadApp() }
|
||||
}
|
||||
(Some(listener), configureScanner(listener, c.scanDirectories, c.scanInterval))
|
||||
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, None)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
server.start()
|
||||
new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), scanner, 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)
|
||||
}
|
||||
trait Reload { def reloadApp(): Unit }
|
||||
private class StopServer(serverReference: Reference[Server], reloadReference: Option[Reference[Reload]], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: JLogger) extends Stoppable
|
||||
{
|
||||
def reload(): Unit = on(reloadReference)(_.reloadApp())
|
||||
private def on[T](refOpt: Option[Reference[T]])(f: T => Unit): Unit = refOpt.foreach(ref => onReferenced(ref.get)(f))
|
||||
private def onReferenced[T](t: T)(f: T => Unit): Unit = if(t == null) () else f(t)
|
||||
def stop()
|
||||
{
|
||||
onReferenced(serverReference.get)(_.stop())
|
||||
on(scannerReferenceOpt)(_.stop())
|
||||
Log.setLog(oldLog)
|
||||
}
|
||||
}
|
||||
private def reload(server: Server, reconfigure: => Unit, log: Logger)
|
||||
{
|
||||
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.")
|
||||
}
|
||||
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 JettyLoggerBase(delegate) with JLogger
|
||||
{
|
||||
def getLogger(name: String) = this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import org.mortbay.jetty.{Handler, Server}
|
||||
import org.mortbay.jetty.nio.SelectChannelConnector
|
||||
import org.mortbay.jetty.webapp.{WebAppClassLoader, WebAppContext}
|
||||
import org.mortbay.log.{Log, Logger => JLogger}
|
||||
import org.mortbay.util.Scanner
|
||||
import org.mortbay.xml.XmlConfiguration
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import org.eclipse.jetty.server.{Server, Handler}
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector
|
||||
import org.eclipse.jetty.webapp.{WebAppClassLoader, WebAppContext}
|
||||
import org.eclipse.jetty.util.log.{Log, Logger => JLogger}
|
||||
import org.eclipse.jetty.util.Scanner
|
||||
import org.eclipse.jetty.xml.XmlConfiguration
|
||||
Loading…
Reference in New Issue