init default tip
authorAlexandre Bertails <bertails@w3.org>
Wed, 24 Aug 2011 12:23:24 -0400
changeset 0 050e9e6cf8d9
init
.hgignore
README.txt
project/build.scala
project/plugins/build.sbt
sbt
src/main/resources/scripts/yourapp.js
src/main/resources/styles/yourapp.css
src/main/resources/templates/hello.ssp
src/main/scala/Main.scala
src/main/scala/Model.scala
src/main/scala/Util.scala
src/test/scala/Test.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,17 @@
+syntax: glob
+target/
+lib_managed/
+src_managed/
+lift_example/
+project/boot/
+.classpath
+.project
+.manager
+*~
+*.class
+*.log
+*\#
+src/main/scala.egp
+src/test/scala.egp
+sbt-launch.jar
+.scala_dependencies
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,51 @@
+Scala YourApp demo
+------------------
+
+It depends on:
+* Java 6
+* that's all :-)
+
+It comes with
+* sbt project
+* generic sbt launcher
+* jar packager (assembly)
+* eclipse plugin for sbt
+* Web framework (Unfiltered)
+* embedded Web server (Jetty)
+* tests for web api (specs)
+* templating system (Scalate)
+* http client (Dispatch)
+* logger (slf4j)
+
+How to start geeking
+--------------------
+
+* to launch sbt
+
+$ ./sbt
+
+* to auto-compile the source
+
+> ~ compile
+
+* to launch tests under sbt (will cache all the dependencies the first time, can take a while)
+
+> test
+
+* to run the Web App
+
+> run
+
+or
+
+> run 8080
+
+* to package the application as a standalone jar (ends up under target/ directory)
+
+> assembly
+
+* to generate the eclipse configuration
+
+> eclipse same-targets
+
+Have fun!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project/build.scala	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,70 @@
+import sbt._
+import Keys._
+
+// some usefull libraries
+// they are pulled only if used
+object Dependencies {
+  val specs = "org.scala-tools.testing" % "specs_2.9.0-1" % "1.6.8" % "test"
+  val scalatest = "org.scalatest" % "scalatest_2.9.0" % "1.6.1" % "test"
+  val salat = "com.novus" %% "salat-core" % "0.0.8-SNAPSHOT"
+  val dispatch = "net.databinder" %% "dispatch-http" % "0.8.4"
+  val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % "0.4.1"
+  val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % "0.4.1"
+  val unfiltered_scalate = "net.databinder" %% "unfiltered-scalate" % "0.4.1"
+  val unfiltered_json = "net.databinder" %% "unfiltered-json" % "0.4.1"
+  val unfiltered_spec = "net.databinder" %% "unfiltered-spec" % "0.4.1" % "test"
+  val slf4jSimple = "org.slf4j" % "slf4j-simple" % "1.6.1"
+  val antiXML = "com.codecommit" %% "anti-xml" % "0.3-SNAPSHOT"
+}
+
+// some usefull repositories
+object Resolvers {
+  val novus = "repo.novus snaps" at "http://repo.novus.com/snapshots/"
+}
+
+// general build settings
+object BuildSettings {
+
+  val buildOrganization = "org.w3"
+  val buildVersion      = "0.1-SNAPSHOT"
+  val buildScalaVersion = "2.9.0-1"
+
+  val buildSettings = Defaults.defaultSettings ++ Seq (
+    organization := buildOrganization,
+    version      := buildVersion,
+    scalaVersion := buildScalaVersion,
+    parallelExecution in Test := false,
+    scalacOptions ++= Seq("-deprecation", "-unchecked")
+  )
+
+}
+
+object YourProjectBuild extends Build {
+
+  import Dependencies._
+  import Resolvers._
+  import BuildSettings._
+
+  val yourProjectSettings =
+    Seq(
+      resolvers += ScalaToolsReleases,
+      resolvers += ScalaToolsSnapshots,
+      libraryDependencies += specs,
+      libraryDependencies += unfiltered_spec,
+      libraryDependencies += dispatch,
+      libraryDependencies += unfiltered_filter,
+      libraryDependencies += unfiltered_jetty,
+      libraryDependencies += unfiltered_scalate,
+      libraryDependencies += slf4jSimple
+    )
+
+  lazy val yourProject = Project(
+    id = "your-project",
+    base = file("."),
+    settings = buildSettings ++ yourProjectSettings ++ sbtassembly.Plugin.assemblySettings
+  )
+  
+
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project/plugins/build.sbt	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,11 @@
+libraryDependencies <+= (sbtVersion) { sv => "com.eed3si9n" %% "sbt-assembly" % ("sbt" + sv + "_0.4") }
+
+resolvers += {
+  val typesafeRepoUrl = new java.net.URL("http://repo.typesafe.com/typesafe/releases")
+  val pattern = Patterns(false, "[organisation]/[module]/[sbtversion]/[revision]/[type]s/[module](-[classifier])-[revision].[ext]")
+  Resolver.url("Typesafe Repository", typesafeRepoUrl)(pattern)
+}
+
+libraryDependencies <<= (libraryDependencies, sbtVersion) { (deps, version) => 
+  deps :+ ("com.typesafe.sbteclipse" %% "sbteclipse" % "1.3-RC3" extra("sbtversion" -> version))
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbt	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+dir=$(dirname $0)
+cd "$dir"
+
+if [ ! -f sbt-launch.jar ]; then
+    wget http://typesafe.artifactoryonline.com/typesafe/ivy-releases/org.scala-tools.sbt/sbt-launch/0.10.1/sbt-launch.jar
+fi
+
+java -Xmx512M -jar -Dfile.encoding=UTF8 -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m "$dir/sbt-launch.jar" "$@"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/templates/hello.ssp	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,2 @@
+<%@ val model: org.w3.yourapp.model.Person %>
+<p>Hello ${model.name}, what is the weather like in ${model.city}</p>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Main.scala	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,79 @@
+package org.w3.yourapp
+
+import org.w3.yourapp.util.ResourceManager.fromClasspath
+
+import javax.servlet._
+import javax.servlet.http._
+import unfiltered.request._
+import unfiltered.response._
+import unfiltered.scalate._
+import unfiltered.jetty._
+
+import org.fusesource.scalate.TemplateEngine
+import org.slf4j.{Logger, LoggerFactory}
+
+// holds some Unfiltered plans
+object YourProjectApp {
+  
+  val logger:Logger = LoggerFactory.getLogger(this.getClass)
+
+  val engine = {
+    val templates = fromClasspath("templates/")
+    logger.debug("Template directory extracted at " + templates.getAbsolutePath)
+    val templateDirs = List(templates)
+//    val scalateMode = "production"
+    val scalateMode = "development"
+    new TemplateEngine(templateDirs, scalateMode)
+  }
+
+  val echo = unfiltered.filter.Planify {
+    case Path(Seg("echo" :: p :: Nil)) => ResponseString(p)
+  }
+
+  val hello = unfiltered.filter.Planify {
+    case req @ Path(Seg("hello" :: username :: Nil)) => {
+      val person = model.Person(username, "Cambridge")
+      Ok ~> Scalate(req, "hello.ssp", "model" -> person)(engine)
+    }
+  }
+
+}
+
+import YourProjectApp._
+
+object VirtualTrainerAppMain {
+
+  val logger:Logger = LoggerFactory.getLogger(this.getClass)
+
+  // regular Java main
+  def main(args: Array[String]) {
+    
+    // can provide the port as first argument
+    // default to 2719
+    val port = args.toList.headOption map { _.toInt } getOrElse 2719
+    
+    // configures and launches a Jetty server
+    unfiltered.jetty.Http(port).filter {
+      // a jee Servlet filter that logs request
+      new Filter {
+        def destroy():Unit = ()
+        def doFilter(request:ServletRequest, response:ServletResponse, chain:FilterChain):Unit = {
+          val r:HttpServletRequest = request.asInstanceOf[HttpServletRequest]
+   		  val method = r.getMethod
+          val uri = r.getRequestURI 
+          logger.info("%s %s" format (method, uri))
+          chain.doFilter(request, response)
+        }
+        def init(filterConfig:FilterConfig):Unit = ()
+      }
+    // serves static files
+    }.context("/scripts"){ ctx:ContextBuilder =>
+      ctx.resources(fromClasspath("scripts/").toURI.toURL)
+    }.context("/styles"){ ctx:ContextBuilder =>
+      ctx.resources(fromClasspath("styles/").toURI.toURL)
+    // Unfiltered filters
+    }.filter(echo).filter(hello).run()
+    
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Model.scala	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,3 @@
+package org.w3.yourapp.model
+
+case class Person(name:String, city:String)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Util.scala	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,62 @@
+package org.w3.yourapp.util
+
+import java.io.{File, FileWriter}
+import java.util.jar._
+import scala.collection.JavaConversions._
+import scala.io.Source
+import java.net.{URL, URLDecoder}
+import org.slf4j.{Logger, LoggerFactory}
+
+/** usefull stuff to read resources from the classpath */
+object ResourceManager {
+  
+  val logger:Logger = LoggerFactory.getLogger(this.getClass)
+
+  val clazz:Class[_] = this.getClass
+  val classloader = this.getClass.getClassLoader
+  
+  /** http://www.uofr.net/~greg/java/get-resource-listing.html
+   */
+  def getResourceListing(path:String):List[String] = {
+    var dirURL:URL = classloader.getResource(path)
+    if (dirURL != null && dirURL.getProtocol == "file") {
+      /* A file path: easy enough */
+      new File(dirURL.toURI).list.toList
+    } else {
+      if (dirURL == null) {
+        val me = clazz.getName().replace(".", "/")+".class"
+        dirURL = classloader.getResource(me)
+      }
+      if (dirURL.getProtocol == "jar") {
+        val jarPath = dirURL.getPath.substring(5, dirURL.getPath().indexOf("!"))
+        val jar:JarFile = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))
+        val entries = jar.entries filter { _.getName startsWith path } map { e => {
+          var entry = e.getName substring path.length
+          val checkSubdir = entry indexOf "/"
+          if (checkSubdir >= 0) entry = entry.substring(0, checkSubdir)
+          entry
+        } }
+        entries filterNot { _.isEmpty } toList
+      } else
+        sys.error("Cannot list files for URL "+dirURL);
+    }
+  }
+  
+  /** extract a path found in the classpath
+   * 
+   *  @return the file on disk
+   */
+  def fromClasspath(path:String):File = {
+    val dir = new File("/tmp", "virtual-trainer-" + scala.util.Random.nextInt(10000).toString)
+    if (! dir.mkdir()) logger.error("Couldn't extract %s from jar to %s" format (path, dir.getAbsolutePath))
+    val entries = getResourceListing(path) foreach { entry =>
+      val url = classloader.getResource(path + entry)
+      val content = Source.fromURL(url, "UTF-8").getLines.mkString("\n")
+      val writer = new FileWriter(new File(dir, entry))
+      writer.write(content)
+      writer.close()
+    }
+    dir
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/Test.scala	Wed Aug 24 12:23:24 2011 -0400
@@ -0,0 +1,37 @@
+package org.w3.yourapp
+
+import org.specs._
+import java.net.URL
+import unfiltered.response._
+import unfiltered.request._
+import dispatch._
+
+object EchoSpec extends Specification with unfiltered.spec.jetty.Served {
+
+  def setup = { _.filter(YourProjectApp.echo) }
+
+  val echoAbracadabra:Request = host / "echo" / "abracadabra"
+    
+  "GET on /echo/abracadabra" should {
+    "return abracadabra" in {
+      val body:String = Http(echoAbracadabra as_str)
+          body must_== "abracadabra"
+    }
+  }
+  
+}
+
+object HelloSpec extends Specification with unfiltered.spec.jetty.Served {
+
+  def setup = { _.filter(YourProjectApp.hello) }
+
+  val helloTimBL:Request = host / "hello" / "TimBL"
+    
+  "GET on /hello/TimBL" should {
+    "return a nice message" in {
+      val body:String = Http(helloTimBL as_str)
+          body must_== "<p>Hello TimBL, what is the weather like in Cambridge</p>"
+    }
+  }
+  
+}