From b0e70ab775b1f63573716dfa185b0d1ba583ee0d Mon Sep 17 00:00:00 2001 From: jboner Date: Wed, 5 Aug 2009 14:41:23 +0200 Subject: [PATCH] added support for running akka as part of Lift app in Jetty, made akka web app aware, added sample module --- samples-lift/akka-samples-lift.iml | 98 ++++++++ samples-lift/config/akka.conf | 64 +++++ samples-lift/pom.xml | 236 ++++++++++++++++++ .../main/scala/bootstrap/liftweb/Boot.scala | 58 +++++ .../example/akka/SimpleService.scala | 42 ++++ .../scala/eu/getintheloop/example/comet/.keep | 0 .../scala/eu/getintheloop/example/model/.keep | 0 .../eu/getintheloop/example/snippet/.keep | 0 .../example/snippet/HelloWorld.scala | 6 + .../scala/eu/getintheloop/example/view/.keep | 0 samples-lift/src/main/webapp/index.html | 5 + .../main/webapp/templates-hidden/default.html | 15 ++ samples-lift/src/test/scala/LiftConsole.scala | 15 ++ samples-lift/src/test/scala/RunWebApp.scala | 28 +++ .../eu/getintheloop/example/AppTest.scala | 75 ++++++ 15 files changed, 642 insertions(+) create mode 100644 samples-lift/akka-samples-lift.iml create mode 100644 samples-lift/config/akka.conf create mode 100644 samples-lift/pom.xml create mode 100644 samples-lift/src/main/scala/bootstrap/liftweb/Boot.scala create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/akka/SimpleService.scala create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/comet/.keep create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/model/.keep create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/snippet/.keep create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/snippet/HelloWorld.scala create mode 100644 samples-lift/src/main/scala/eu/getintheloop/example/view/.keep create mode 100644 samples-lift/src/main/webapp/index.html create mode 100644 samples-lift/src/main/webapp/templates-hidden/default.html create mode 100644 samples-lift/src/test/scala/LiftConsole.scala create mode 100644 samples-lift/src/test/scala/RunWebApp.scala create mode 100644 samples-lift/src/test/scala/eu/getintheloop/example/AppTest.scala diff --git a/samples-lift/akka-samples-lift.iml b/samples-lift/akka-samples-lift.iml new file mode 100644 index 0000000000..70037ebd8b --- /dev/null +++ b/samples-lift/akka-samples-lift.iml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples-lift/config/akka.conf b/samples-lift/config/akka.conf new file mode 100644 index 0000000000..cfe4797f96 --- /dev/null +++ b/samples-lift/config/akka.conf @@ -0,0 +1,64 @@ +##################### +# Akka Config File # +################### + +# This file has all the default settings, so all these could be remove with no visible effect. +# Modify as needed. + + + filename = "./logs/akka.log" + roll = "daily" # Options: never, hourly, daily, sunday/monday/... + level = "debug" # Options: fatal, critical, error, warning, info, debug, trace + console = on + # syslog_host = "" + # syslog_server_name = "" + + + + version = "v0.5" + + + timeout = 5000 # default timeout for future based invocations + concurrent-mode = off # if turned on, then the same actor instance is allowed to execute concurrently - + # e.g. departing from the actor model for better performance + serialize-messages = on # does a deep clone of (non-primitive) messages to ensure immutability + + + + service = on + restart-on-collision = off # (not implemented yet) if 'on' then it reschedules the transaction, + # if 'off' then throws an exception or rollback for user to handle + wait-for-completion = 100 # how long time in millis a transaction should be given time to complete when a collision is detected + wait-nr-of-times = 3 # the number of times it should check for completion of a pending transaction upon collision + distributed = off # not implemented yet + + + + service = on + hostname = "localhost" + port = 9999 + connection-timeout = 1000 # in millis + + + + service = on + hostname = "localhost" + port = 9998 + + + + system = "cassandra" # Options: cassandra (coming: terracotta, redis, tokyo-cabinet, tokyo-tyrant, voldemort, memcached, hazelcast) + + + service = on + storage-format = "java" # Options: java, scala-json, java-json + blocking = false # inserts and queries should be blocking or not + + + service = on + pidfile = "akka.pid" + + + + + diff --git a/samples-lift/pom.xml b/samples-lift/pom.xml new file mode 100644 index 0000000000..220dd37cd5 --- /dev/null +++ b/samples-lift/pom.xml @@ -0,0 +1,236 @@ + + + 4.0.0 + + akka-samples-lift + Akka Lift Samples Module + + war + + + akka + se.scalablesolutions.akka + 0.5 + + + + 1.1-SNAPSHOT + + + + + repo1.maven + Maven Main Repository + http://repo1.maven.org/maven2 + + + project.embedded.module + Project Embedded Repository + file://${basedir}/../embedded-repo + + + scala-tools-snapshots + Scala-Tools Maven2 Snapshot Repository + http://scala-tools.org/repo-snapshots + + + scala-tools + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + lag + Configgy's' Repository + http://www.lag.net/repo + + + maven2-repository.dev.java.net + Java.net Repository for Maven + http://download.java.net/maven/2 + + + java.net + Java.net Legacy Repository for Maven + http://download.java.net/maven/1 + legacy + + + guiceyfruit.release + GuiceyFruit Release Repository + http://guiceyfruit.googlecode.com/svn/repo/releases/ + + false + + + true + + + + guiceyfruit.snapshot + GuiceyFruit Snapshot Repository + http://guiceyfruit.googlecode.com/svn/repo/snapshots/ + + true + + + false + + + + google-maven-repository + Google Maven Repository + http://google-maven-repository.googlecode.com/svn/repository/ + + + repository.codehaus.org + Codehaus Maven Repository + http://repository.codehaus.org + + true + + + + repository.jboss.org + JBoss Repository for Maven + http://repository.jboss.org/maven2 + + false + + + + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + + + + org.codehaus.aspectwerkz + aspectwerkz-nodeps-jdk5 + 2.1 + + + se.scalablesolutions.akka + akka-kernel + 0.5 + + + se.scalablesolutions.akka + akka-util-java + 0.5 + + + javax.ws.rs + jsr311-api + 1.0 + + + org.scala-lang + scala-library + ${scala.version} + + + net.liftweb + lift-util + ${lift.version} + + + net.liftweb + lift-webkit + ${lift.version} + + + javax.servlet + servlet-api + 2.5 + provided + + + junit + junit + 4.5 + test + + + org.mortbay.jetty + jetty + [6.1.6,) + test + + + + org.scala-lang + scala-compiler + ${scala.version} + test + + + + + src/main/scala + src/test/scala + + + org.scala-tools + maven-scala-plugin + + + + compile + testCompile + + + + + ${scala.version} + + + + org.mortbay.jetty + maven-jetty-plugin + + / + 5 + + + + net.sf.alchim + yuicompressor-maven-plugin + + + + compress + + + + + true + + + + + + false + config + + akka.conf + + + + + + + + org.scala-tools + maven-scala-plugin + + ${scala.version} + + + + + diff --git a/samples-lift/src/main/scala/bootstrap/liftweb/Boot.scala b/samples-lift/src/main/scala/bootstrap/liftweb/Boot.scala new file mode 100644 index 0000000000..5e3b9c1591 --- /dev/null +++ b/samples-lift/src/main/scala/bootstrap/liftweb/Boot.scala @@ -0,0 +1,58 @@ +package bootstrap.liftweb + +import _root_.net.liftweb.util._ +import _root_.net.liftweb.http._ +import _root_.net.liftweb.sitemap._ +import _root_.net.liftweb.sitemap.Loc._ +import Helpers._ +import _root_.net.liftweb.http.auth._ + +import se.scalablesolutions.akka.kernel.state.{TransactionalState, CassandraStorageConfig} +import se.scalablesolutions.akka.kernel.actor.{SupervisorFactory, Actor} +import se.scalablesolutions.akka.kernel.config.ScalaConfig._ +import se.scalablesolutions.akka.kernel.util.Logging +import sample.lift.SimpleService + +/** + * A class that's instantiated early and run. It allows the application + * to modify lift's environment + */ +class Boot { + def boot { + // where to search snippet + LiftRules.addToPackages("sample.lift") + + LiftRules.httpAuthProtectedResource.prepend { + case (ParsePath("secure-basic" :: Nil, _, _, _)) => Full(AuthRole("admin")) + } + + LiftRules.authentication = HttpBasicAuthentication("lift") { + case ("someuser", "1234", req) => { + Log.info("You are now authenticated !") + userRoles(AuthRole("admin")) + true + } + } + + LiftRules.passNotFoundToChain = true + + object factory extends SupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(OneForOne, 3, 100), + Supervise( + new SimpleService, + LifeCycle(Permanent, 100) + ) + :: Nil) + } + } + val supervisor = factory.newSupervisor + supervisor.startSupervisor + + // Build SiteMap + // val entries = Menu(Loc("Home", List("index"), "Home")) :: Nil + // LiftRules.setSiteMap(SiteMap(entries:_*)) + } +} + diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/akka/SimpleService.scala b/samples-lift/src/main/scala/eu/getintheloop/example/akka/SimpleService.scala new file mode 100644 index 0000000000..f7af8a6ef7 --- /dev/null +++ b/samples-lift/src/main/scala/eu/getintheloop/example/akka/SimpleService.scala @@ -0,0 +1,42 @@ +package sample.lift + +import se.scalablesolutions.akka.kernel.state.{TransactionalState, CassandraStorageConfig} +import se.scalablesolutions.akka.kernel.actor.{SupervisorFactory, Actor} +import se.scalablesolutions.akka.kernel.config.ScalaConfig._ +import se.scalablesolutions.akka.kernel.util.Logging + +import javax.ws.rs.core.MultivaluedMap +import javax.ws.rs.{GET, POST, Path, Produces, WebApplicationException, Consumes} + +/** + * Try service out by invoking (multiple times): + *
+ * curl http://localhost:9998/liftcount
+ * 
+ * Or browse to the URL from a web browser. + */ +@Path("/liftcount") +class SimpleService extends Actor { + makeTransactionRequired + + case object Tick + private val KEY = "COUNTER"; + private var hasStartedTicking = false; + private val storage = TransactionalState.newPersistentMap(CassandraStorageConfig()) + + @GET + @Produces(Array("text/html")) + def count = (this !! Tick).getOrElse(

Error in counter

) + + override def receive: PartialFunction[Any, Unit] = { + case Tick => if (hasStartedTicking) { + val counter = storage.get(KEY).get.asInstanceOf[Integer].intValue + storage.put(KEY, new Integer(counter + 1)) + reply(

Tick: {counter + 1}

) + } else { + storage.put(KEY, new Integer(0)) + hasStartedTicking = true + reply(

Tick: 0

) + } + } +} diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/comet/.keep b/samples-lift/src/main/scala/eu/getintheloop/example/comet/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/model/.keep b/samples-lift/src/main/scala/eu/getintheloop/example/model/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/snippet/.keep b/samples-lift/src/main/scala/eu/getintheloop/example/snippet/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/snippet/HelloWorld.scala b/samples-lift/src/main/scala/eu/getintheloop/example/snippet/HelloWorld.scala new file mode 100644 index 0000000000..75c0347ecd --- /dev/null +++ b/samples-lift/src/main/scala/eu/getintheloop/example/snippet/HelloWorld.scala @@ -0,0 +1,6 @@ +package eu.getintheloop.example.snippet + +class HelloWorld { + def howdy = Welcome to lift-akka at {new _root_.java.util.Date} +} + diff --git a/samples-lift/src/main/scala/eu/getintheloop/example/view/.keep b/samples-lift/src/main/scala/eu/getintheloop/example/view/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples-lift/src/main/webapp/index.html b/samples-lift/src/main/webapp/index.html new file mode 100644 index 0000000000..41f9b1b7d8 --- /dev/null +++ b/samples-lift/src/main/webapp/index.html @@ -0,0 +1,5 @@ + +

Welcome to your project!

+

+
+ diff --git a/samples-lift/src/main/webapp/templates-hidden/default.html b/samples-lift/src/main/webapp/templates-hidden/default.html new file mode 100644 index 0000000000..412bcd9620 --- /dev/null +++ b/samples-lift/src/main/webapp/templates-hidden/default.html @@ -0,0 +1,15 @@ + + + + + + + eu.getintheloop.example:lift-akka:1.0-SNAPSHOT + + + + + + + + diff --git a/samples-lift/src/test/scala/LiftConsole.scala b/samples-lift/src/test/scala/LiftConsole.scala new file mode 100644 index 0000000000..f8f517e97a --- /dev/null +++ b/samples-lift/src/test/scala/LiftConsole.scala @@ -0,0 +1,15 @@ +import _root_.bootstrap.liftweb.Boot +import _root_.scala.tools.nsc.MainGenericRunner + +object LiftConsole { + def main(args : Array[String]) { + // Instantiate your project's Boot file + val b = new Boot() + // Boot your project + b.boot + // Now run the MainGenericRunner to get your repl + MainGenericRunner.main(args) + // After the repl exits, then exit the scala script + exit(0) + } +} diff --git a/samples-lift/src/test/scala/RunWebApp.scala b/samples-lift/src/test/scala/RunWebApp.scala new file mode 100644 index 0000000000..3ac7502f44 --- /dev/null +++ b/samples-lift/src/test/scala/RunWebApp.scala @@ -0,0 +1,28 @@ +import _root_.org.mortbay.jetty.Connector +import _root_.org.mortbay.jetty.Server +import _root_.org.mortbay.jetty.webapp.WebAppContext + +object RunWebApp extends Application { + val server = new Server(8080) + val context = new WebAppContext() + context.setServer(server) + context.setContextPath("/") + context.setWar("src/main/webapp") + + server.addHandler(context) + + try { + println(">>> STARTING EMBEDDED JETTY SERVER, PRESS ANY KEY TO STOP") + server.start() + while (System.in.available() == 0) { + Thread.sleep(5000) + } + server.stop() + server.join() + } catch { + case exc : Exception => { + exc.printStackTrace() + System.exit(100) + } + } +} diff --git a/samples-lift/src/test/scala/eu/getintheloop/example/AppTest.scala b/samples-lift/src/test/scala/eu/getintheloop/example/AppTest.scala new file mode 100644 index 0000000000..babdbdd638 --- /dev/null +++ b/samples-lift/src/test/scala/eu/getintheloop/example/AppTest.scala @@ -0,0 +1,75 @@ +package eu.getintheloop.example + +import _root_.java.io.File +import _root_.junit.framework._ +import Assert._ +import _root_.scala.xml.XML +import _root_.net.liftweb.util._ + +object AppTest { + def suite: Test = { + val suite = new TestSuite(classOf[AppTest]) + suite + } + + def main(args : Array[String]) { + _root_.junit.textui.TestRunner.run(suite) + } +} + +/** + * Unit test for simple App. + */ +class AppTest extends TestCase("app") { + + /** + * Rigourous Tests :-) + */ + def testOK() = assertTrue(true) + // def testKO() = assertTrue(false); + + /** + * Tests to make sure the project's XML files are well-formed. + * + * Finds every *.html and *.xml file in src/main/webapp (and its + * subdirectories) and tests to make sure they are well-formed. + */ + def testXml() = { + var failed: List[File] = Nil + + def handledXml(file: String) = + file.endsWith(".xml") + + def handledXHtml(file: String) = + file.endsWith(".html") || file.endsWith(".htm") || file.endsWith(".xhtml") + + def wellFormed(file: File) { + if (file.isDirectory) + for (f <- file.listFiles) wellFormed(f) + + if (file.isFile && handledXml(file.getName)) { + try { + XML.loadFile(file) + } catch { + case e: _root_.org.xml.sax.SAXParseException => failed = file :: failed + } + } + if (file.isFile && handledXHtml(file.getName)) { + PCDataXmlParser(new java.io.FileInputStream(file.getAbsolutePath)) match { + case Full(_) => // file is ok + case _ => failed = file :: failed + } + } + } + + wellFormed(new File("src/main/webapp")) + + val numFails = failed.size + if (numFails > 0) { + val fileStr = if (numFails == 1) "file" else "files" + val msg = "Malformed XML in " + numFails + " " + fileStr + ": " + failed.mkString(", ") + println(msg) + fail(msg) + } + } +}