diff --git a/project/build/SbtProject.scala b/project/build/SbtProject.scala index 23d538774..184ffb795 100644 --- a/project/build/SbtProject.scala +++ b/project/build/SbtProject.scala @@ -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) } \ No newline at end of file diff --git a/src/main/scala/sbt/WebApp.scala b/src/main/scala/sbt/WebApp.scala index f6b2725cf..5d53917ee 100644 --- a/src/main/scala/sbt/WebApp.scala +++ b/src/main/scala/sbt/WebApp.scala @@ -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) - } - } - } -} +} \ No newline at end of file diff --git a/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ b/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ new file mode 100644 index 000000000..29f86a4a8 --- /dev/null +++ b/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ @@ -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 + } +} diff --git a/src/main/scala/sbt/jetty/LazyJettyRun6.scala b/src/main/scala/sbt/jetty/LazyJettyRun6.scala new file mode 100644 index 000000000..30ea2c787 --- /dev/null +++ b/src/main/scala/sbt/jetty/LazyJettyRun6.scala @@ -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 + } +} diff --git a/src/main/scala/sbt/jetty/LazyJettyRun7.scala b/src/main/scala/sbt/jetty/LazyJettyRun7.scala new file mode 100644 index 000000000..84ca8c7d5 --- /dev/null +++ b/src/main/scala/sbt/jetty/LazyJettyRun7.scala @@ -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 + } +} diff --git a/src/main/scala/sbt/jetty/jetty6.imports b/src/main/scala/sbt/jetty/jetty6.imports new file mode 100644 index 000000000..3b20abbf1 --- /dev/null +++ b/src/main/scala/sbt/jetty/jetty6.imports @@ -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 \ No newline at end of file diff --git a/src/main/scala/sbt/jetty/jetty7.imports b/src/main/scala/sbt/jetty/jetty7.imports new file mode 100644 index 000000000..35f18d862 --- /dev/null +++ b/src/main/scala/sbt/jetty/jetty7.imports @@ -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 \ No newline at end of file