From 7133c42c00e1c431faf7fee8acda85a9af54a1e0 Mon Sep 17 00:00:00 2001 From: Jonas Boner Date: Thu, 19 Feb 2009 15:57:59 +0100 Subject: [PATCH] added basic JAX-RS, Jersey and Grizzly HTTP server support --- akka.iws | 249 ++++++++++++------ api-java/pom.xml | 3 - .../api/ActiveObjectGuiceConfigurator.java | 2 + kernel/kernel.iml | 144 ++++++++++ kernel/pom.xml | 31 ++- kernel/src/main/scala/ActiveObject.scala | 42 ++- kernel/src/main/scala/Configuration.scala | 54 +--- kernel/src/main/scala/Kernel.scala | 60 ++++- kernel/src/main/scala/Logging.scala | 68 +++++ kernel/src/test/scala/ActiveObjectSuite.scala | 89 ++++--- kernel/src/test/scala/restManagerSpec.scala | 31 +++ pom.xml | 15 +- 12 files changed, 597 insertions(+), 191 deletions(-) create mode 100755 kernel/src/main/scala/Logging.scala create mode 100755 kernel/src/test/scala/restManagerSpec.scala diff --git a/akka.iws b/akka.iws index d4720804e1..9151bf8f40 100755 --- a/akka.iws +++ b/akka.iws @@ -1,11 +1,22 @@ - - + + + + + + + + + + + + + - + @@ -64,28 +75,35 @@ - + - - + + - + - - + + - - - - + + + + + + + + + + + @@ -93,27 +111,16 @@ - + - - + + - - - - - - - - - - - - + @@ -209,6 +216,14 @@ - + + + - + - - + + + + + localhost @@ -429,33 +495,33 @@ - + - - - + + + - + - - - - - - + + + + + + - - - - - + + + + + @@ -491,9 +557,23 @@ + + + + + + + - + + + + + + + + @@ -507,67 +587,86 @@ - + + + + + + + + - + - - - - - - - - - - - - - - - + - + + + + + + + + - + - + - - - - + + - + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/api-java/pom.xml b/api-java/pom.xml index 469bef7599..8ab294bad4 100755 --- a/api-java/pom.xml +++ b/api-java/pom.xml @@ -55,9 +55,6 @@ **/Abstract* - - - diff --git a/api-java/src/main/java/com/scalablesolutions/akka/api/ActiveObjectGuiceConfigurator.java b/api-java/src/main/java/com/scalablesolutions/akka/api/ActiveObjectGuiceConfigurator.java index 4e901e2fe4..655a84a68f 100755 --- a/api-java/src/main/java/com/scalablesolutions/akka/api/ActiveObjectGuiceConfigurator.java +++ b/api-java/src/main/java/com/scalablesolutions/akka/api/ActiveObjectGuiceConfigurator.java @@ -1,5 +1,7 @@ package com.scalablesolutions.akka.api; +import com.scalablesolutions.akka.kernel.configuration.*; + import com.google.inject.*; import com.google.inject.jsr250.ResourceProviderFactory; diff --git a/kernel/kernel.iml b/kernel/kernel.iml index e5600b6d14..cdb7948e9f 100755 --- a/kernel/kernel.iml +++ b/kernel/kernel.iml @@ -68,6 +68,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kernel/pom.xml b/kernel/pom.xml index 76c1be5e58..39a8c83736 100755 --- a/kernel/pom.xml +++ b/kernel/pom.xml @@ -50,6 +50,26 @@ guice-jsr250 2.0-SNAPSHOT + + com.sun.grizzly + grizzly-servlet-webserver + 1.8.6.3 + + + com.sun.jersey + jersey-server + 1.0.1 + + + com.sun.jersey + jersey-json + 1.0.1 + + + com.sun.jersey + jersey-atom + 1.0.1 + org.specs specs @@ -113,6 +133,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + **/Abstract* + + + org.codehaus.mojo cobertura-maven-plugin @@ -123,7 +152,7 @@ html - + diff --git a/kernel/src/main/scala/ActiveObject.scala b/kernel/src/main/scala/ActiveObject.scala index 6e07aa8159..53a3f3818c 100755 --- a/kernel/src/main/scala/ActiveObject.scala +++ b/kernel/src/main/scala/ActiveObject.scala @@ -36,6 +36,13 @@ object ActiveObject { proxy).asInstanceOf[T] } + def newInstance[T](intf: Class[_], target: AnyRef, timeout: Int): T = { + val proxy = new ActiveObjectProxy(intf, target.getClass, timeout) + proxy.setTargetInstance(target) + supervise(proxy) + newInstance(intf, proxy) + } + def supervise(restartStrategy: RestartStrategy, components: List[Worker]): Supervisor = { object factory extends SupervisorFactory { override def getSupervisorConfig = SupervisorConfig(restartStrategy, components) @@ -44,6 +51,14 @@ object ActiveObject { supervisor ! scala.actors.behavior.Start supervisor } + + private def supervise(proxy: ActiveObjectProxy): Supervisor = + supervise( + RestartStrategy(OneForOne, 5, 1000), + Worker( + proxy.server, + LifeCycle(Permanent, 100)) + :: Nil) } /** @@ -58,10 +73,16 @@ class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: I override def body: PartialFunction[Any, Unit] = { case invocation: Invocation => try { + println("==========> " + invocation) reply(ErrRef(invocation.invoke)) } catch { - case e: InvocationTargetException => reply(ErrRef({ throw e.getTargetException })) - case e => reply(ErrRef({ throw e })) + case e: InvocationTargetException => + val te = e.getTargetException + te.printStackTrace + reply(ErrRef({ throw te })) + case e => + e.printStackTrace + reply(ErrRef({ throw e })) } case 'exit => exit; reply() case unexpected => throw new ActiveObjectException("Unexpected message to actor proxy: " + unexpected) @@ -89,10 +110,10 @@ class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: I */ case class Invocation(val method: Method, val args: Array[Object], val target: AnyRef) { method.setAccessible(true); - - def invoke: AnyRef = method.invoke(target, args: _*) - override def toString: String = "Invocation[method: " + method.getName + ", args: " + args + ", target: " + target + "]" + def invoke: AnyRef = method.invoke(target, args:_*) + + override def toString: String = "Invocation [method: " + method.getName + ", args: " + argsToString(args) + ", target: " + target + "]" override def hashCode(): Int = { var result = HashCode.SEED @@ -106,8 +127,13 @@ case class Invocation(val method: Method, val args: Array[Object], val target: A that != null && that.isInstanceOf[Invocation] && that.asInstanceOf[Invocation].method == method && - that.asInstanceOf[Invocation].args == args - that.asInstanceOf[Invocation].target == target + that.asInstanceOf[Invocation].target == target && + isEqual(that.asInstanceOf[Invocation].args, args) } -} + private def isEqual(a1: Array[Object], a2: Array[Object]): Boolean = + (a1 == null && a2 == null) || + (a1 != null && a2 != null && a1.size == a2.size && a1.zip(a2).find(t => t._1 == t._2).isDefined) + + private def argsToString(array: Array[Object]): String = array.foldLeft("(")(_ + " " + _) + ")" +} diff --git a/kernel/src/main/scala/Configuration.scala b/kernel/src/main/scala/Configuration.scala index 3ba50aaa6d..bddb7940bf 100755 --- a/kernel/src/main/scala/Configuration.scala +++ b/kernel/src/main/scala/Configuration.scala @@ -2,7 +2,7 @@ * Copyright (C) 2009 Scalable Solutions. */ -package com.scalablesolutions.akka.api +package com.scalablesolutions.akka.kernel.configuration import com.scalablesolutions.akka.kernel.{ActiveObject, ActiveObjectProxy} import google.inject.{AbstractModule} @@ -52,54 +52,8 @@ abstract class Server extends Configuration // def transform = scala.actors.behavior.SupervisorConfig(restartStrategy.transform, servers.toArray.toList.asInstanceOf[List[Server]].map(_.transform)) //} class Component(@BeanProperty val intf: Class[_], - @BeanProperty val target: Class[_], - @BeanProperty val lifeCycle: LifeCycle, - @BeanProperty val timeout: Int) extends Server { + @BeanProperty val target: Class[_], + @BeanProperty val lifeCycle: LifeCycle, + @BeanProperty val timeout: Int) extends Server { def newWorker(proxy: ActiveObjectProxy) = scala.actors.behavior.Worker(proxy.server, lifeCycle.transform) } - - -// ============================================ - -/** - * @author Jonas Bonér - */ -//object Configuration { -// import com.google.inject.{Module, AbstractModule, CreationException, Guice, Injector, Provides, Singleton, Binder} -// import com.google.inject.jsr250.{ResourceProviderFactory} -// -// private val modules = new ArrayList[Module] -// -// def addModule(module: Module) = modules.add(module) -// -// def supervise(restartStrategy: RestartStrategy, components: Array[Component]): Supervisor = { -// val componentList = components.toList.asInstanceOf[List[Component]] -// -// object defaultModule extends AbstractModule { -// protected def configure { -// bind(classOf[ResourceProviderFactory[_]]) -// //componentList.foreach(c => bind(c.proxy.intf.asInstanceOf[Class[_]]).to(c.proxy.target.getClass.asInstanceOf[Class[_]]).in(classOf[Singleton])) -// } -// -// // @Provides -// // def createJndiContext: Context = { -// // val answer = new JndiContext -// // answer.bind("foo", new AnotherBean("Foo")) -// // answer.bind("xyz", new AnotherBean("XYZ")) -// // answer -// // } -// } -// modules.add(defaultModule) -// val injector = Guice.createInjector(modules) -// -// // swap 'target' in proxy before running supervise -// // componentList.foreach(c => c.proxy.target = injector.getInstance(c.proxy.targetClass)) -// -// ActiveObject.supervise( -// restartStrategy.transform, -// componentList.map(c => scala.actors.behavior.Worker(c.proxy.server, c.lifeCycle.transform))) -// -// } -//} - - diff --git a/kernel/src/main/scala/Kernel.scala b/kernel/src/main/scala/Kernel.scala index 6780aff372..6e27111c61 100755 --- a/kernel/src/main/scala/Kernel.scala +++ b/kernel/src/main/scala/Kernel.scala @@ -1,8 +1,52 @@ -/** - * Copyright (C) 2009 Scalable Solutions. - */ - -package com.scalablesolutions.akka.kernel - -object Kernel { -} +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.kernel + +import com.sun.grizzly.http.SelectorThread +import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory +import java.io.IOException +import java.net.URI +import java.util.{Map, HashMap} +import javax.ws.rs.core.UriBuilder +import javax.ws.rs.{Produces, Path, GET} + +object Kernel extends Logging { + + val SERVER_URL = "http://localhost/" + val SERVER_PORT = 9998 + val BASE_URI = UriBuilder.fromUri(SERVER_URL).port(getPort(SERVER_PORT)).build() + + def getPort(defaultPort: Int) = { + val port = System.getenv("JERSEY_HTTP_PORT") + if (null != port) Integer.parseInt(port) + else defaultPort; + } + +// @GET +// @Produces("application/json") +// @Path("/network/{id: [0-9]+}/{nid}") +// def getUserByNetworkId(@PathParam {val value = "id"} id: Int, @PathParam {val value = "nid"} networkId: String): User = { +// val q = em.createQuery("SELECT u FROM User u WHERE u.networkId = :id AND u.networkUserId = :nid") +// q.setParameter("id", id) +// q.setParameter("nid", networkId) +// q.getSingleResult.asInstanceOf[User] +// } + + def startServer: SelectorThread = { + val initParams = new java.util.HashMap[String, String] + initParams.put( + "com.sun.jersey.config.property.packages", + "com.scalablesolutions.akka.kernel") + log.info("Starting grizzly...") + GrizzlyWebContainerFactory.create(BASE_URI, initParams) + } + + def main(args: Array[String]) { + val threadSelector = startServer + log.info("Akka kernel started at s%", BASE_URI) + System.in.read + threadSelector.stopEndpoint + } +} diff --git a/kernel/src/main/scala/Logging.scala b/kernel/src/main/scala/Logging.scala new file mode 100755 index 0000000000..a0fac55b08 --- /dev/null +++ b/kernel/src/main/scala/Logging.scala @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.kernel + +import net.lag.configgy.Config +import net.lag.logging.Logger + +import java.util.Date +import java.io.StringWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Base trait for all classes that wants to be able use the logging infrastructure. + */ +trait Logging { + @transient val log = Logger.get(this.getClass.getName) +} + +/** + * LoggableException is a subclass of Exception and can be used as the base exception + * for application specific exceptions. + *

+ * It keeps track of the exception is logged or not and also stores the unique id, + * so that it can be carried all along to the client tier and displayed to the end user. + * The end user can call up the customer support using this number. + */ +class LoggableException extends Exception with Logging { + private val uniqueId = getExceptionID + private var originalException: Option[Exception] = None + private var isLogged = false + + def this(baseException: Exception) = { + this() + originalException = Some(baseException) + } + + def logException = synchronized { + if (!isLogged) { + originalException match { + case Some(e) => log.error("Logged Exception [%s] %s", uniqueId, getStackTrace(e)) + case None => log.error("Logged Exception [%s] %s", uniqueId, getStackTrace(this)) + } + isLogged = true + } + } + + def getExceptionID: String = { + val hostname: String = try { + InetAddress.getLocalHost.getHostName + } catch { + case e: UnknownHostException => + log.error("Could not get hostname to generate loggable exception") + "N/A" + } + hostname + "_" + System.currentTimeMillis + } + + def getStackTrace(exception: Throwable): String = { + val sw = new StringWriter + val pw = new PrintWriter(sw) + exception.printStackTrace(pw) + sw.toString + } +} diff --git a/kernel/src/test/scala/ActiveObjectSuite.scala b/kernel/src/test/scala/ActiveObjectSuite.scala index 94bac74f4a..fc984a61f8 100755 --- a/kernel/src/test/scala/ActiveObjectSuite.scala +++ b/kernel/src/test/scala/ActiveObjectSuite.scala @@ -4,6 +4,8 @@ package com.scalablesolutions.akka.kernel +import org.specs.runner.JUnit4 +import org.specs.Specification import scala.actors.behavior._ import scala.actors.annotation.oneway @@ -11,57 +13,55 @@ import scala.actors.annotation.oneway * @author Jonas Bonér */ +class activeObjectSpecTest extends JUnit4(activeObjectSpec) // for JUnit4 and Maven +object activeObjectSpec extends Specification { -// class ActiveObjectSuite extends TestNGSuite { + private var messageLog = "" -// private var messageLog = "" + trait Foo { + def foo(msg: String): String + @oneway def bar(msg: String) + def longRunning + def throwsException + } -// trait Foo { -// def foo(msg: String): String -// @oneway def bar(msg: String) -// def longRunning -// def throwsException -// } + class FooImpl extends Foo { + val bar: Bar = new BarImpl + def foo(msg: String): String = { + messageLog += msg + "return_foo " + } + def bar(msg: String) = bar.bar(msg) + def longRunning = Thread.sleep(10000) + def throwsException = error("expected") + } -// class FooImpl extends Foo { -// val bar: Bar = new BarImpl -// def foo(msg: String): String = { -// messageLog += msg -// "return_foo " -// } -// def bar(msg: String) = bar.bar(msg) -// def longRunning = Thread.sleep(10000) -// def throwsException = error("expected") -// } + trait Bar { + @oneway def bar(msg: String) + } -// trait Bar { -// @oneway def bar(msg: String) -// } + class BarImpl extends Bar { + def bar(msg: String) = { + Thread.sleep(100) + messageLog += msg + } + } -// class BarImpl extends Bar { -// def bar(msg: String) = { -// Thread.sleep(100) -// messageLog += msg -// } -// } +// "make sure default supervisor works correctly" in { +// val foo = ActiveObject.newInstance[Foo](classOf[Foo], classOf[FooImpl], 1000) +// +// val result = foo.foo("foo ") +// messageLog += result +// +// foo.bar("bar ") +// messageLog += "before_bar " +// +// Thread.sleep(500) +// messageLog must equalIgnoreCase("foo return_foo before_bar bar ") +// } -// @BeforeMethod -// def setup = messageLog = "" - -// @Test { val groups=Array("unit") } -// def testCreateGenericServerBasedComponentUsingDefaultSupervisor = { -// val foo = ActiveObject.newInstance[Foo](classOf[Foo], new FooImpl, 1000) - -// val result = foo.foo("foo ") -// messageLog += result - -// foo.bar("bar ") -// messageLog += "before_bar " - -// Thread.sleep(500) -// assert(messageLog === "foo return_foo before_bar bar ") -// } +} // @Test { val groups=Array("unit") } // def testCreateGenericServerBasedComponentUsingCustomSupervisorConfiguration = { // val proxy = new ActiveObjectProxy(new FooImpl, 1000) @@ -137,3 +137,6 @@ import scala.actors.annotation.oneway // assert(true === true) // } // } + + + diff --git a/kernel/src/test/scala/restManagerSpec.scala b/kernel/src/test/scala/restManagerSpec.scala new file mode 100755 index 0000000000..ba2c8d41d1 --- /dev/null +++ b/kernel/src/test/scala/restManagerSpec.scala @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.kernel + +import org.specs.runner.JUnit4 +import org.specs.Specification +import javax.ws.rs.{Produces, Path, GET} + +/** + * @author Jonas Bonér + */ + +@Path("/helloworld") +class HelloWorldResource { + @GET + @Produces(Array("text/plain")) + def getClichedMessage = "Hello World" +} + +class restManagerSpecTest extends JUnit4(restManagerSpec) // for JUnit4 and Maven +object restManagerSpec extends Specification { + + "test" in { + val threadSelector = Kernel.startServer + val reply = System.in.read + println("==============> " + reply) + threadSelector.stopEndpoint + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4e2395692f..00aea8fb57 100755 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,7 @@ - - + 4.0.0 Akka Actor Kernel @@ -44,6 +43,16 @@ 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 + http://download.java.net/maven/1 + legacy + @@ -51,7 +60,7 @@ scala-tools.org Scala-Tools Maven2 Repository http://scala-tools.org/repo-releases - +