From 10f2fcc4866653b80935c284b86b6ee19124655b Mon Sep 17 00:00:00 2001 From: Garrick Evans Date: Mon, 8 Nov 2010 10:54:43 -0800 Subject: [PATCH 01/17] adding back (mist) http work in a new branch. misitfy was too stale. this is WIP - trying to support both SAPI 3.0 and Jetty Continuations at once --- .../src/main/scala/AsyncHttpServlet.scala | 109 ++++++++ akka-http/src/main/scala/Endpoint.scala | 134 ++++++++++ .../src/main/scala/SuspendedRequest.scala | 252 ++++++++++++++++++ akka-http/src/main/scala/Types.scala | 50 ++++ .../src/main/scala/Boot.scala | 45 ++++ .../src/main/scala/SimpleService2.scala | 155 +++++++++++ 6 files changed, 745 insertions(+) create mode 100644 akka-http/src/main/scala/AsyncHttpServlet.scala create mode 100644 akka-http/src/main/scala/Endpoint.scala create mode 100644 akka-http/src/main/scala/SuspendedRequest.scala create mode 100644 akka-http/src/main/scala/Types.scala create mode 100644 akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala create mode 100644 akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala diff --git a/akka-http/src/main/scala/AsyncHttpServlet.scala b/akka-http/src/main/scala/AsyncHttpServlet.scala new file mode 100644 index 0000000000..7e9ccb4855 --- /dev/null +++ b/akka-http/src/main/scala/AsyncHttpServlet.scala @@ -0,0 +1,109 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + +import akka.util.Logging +import javax.servlet.http.{HttpServletResponse, HttpServlet} + +/** + * @author Garrick Evans + */ +class AsyncHttpServlet extends HttpServlet with Logging +{ + import java.util. {Date, TimeZone} + import java.text.SimpleDateFormat + import javax.servlet.ServletConfig + import javax.servlet.http.{HttpServletRequest, HttpServletResponse} + import akka.actor.ActorRegistry + import Types._ + + // + // the root endpoint for this servlet will have been booted already + // use the system property to find out the actor id and cache him + // TODO: currently this is hardcoded but really use a property + // + protected val _root = ActorRegistry.actorsFor("DefaultGridRoot").head + + /** + * Handles the HTTP request method on the servlet, suspends the connection and sends the asynchronous context + * along to the root endpoint in a SuspendedRequest message + */ + protected def _do(request:HttpServletRequest, response:HttpServletResponse)(builder: (()=>Option[tAsyncContext]) => SuspendedRequest) = + { + def suspend:Option[tAsyncContext] = + { + // + // set to effectively "already expired" + // + val gmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z") + gmt.setTimeZone(TimeZone.getTimeZone("GMT")) + + response.setHeader("Expires", gmt.format(new Date)) + response.setHeader("Cache-Control", "no-cache, must-revalidate") + response.setHeader("Connection","close") + + Some(request.asInstanceOf[tAsyncRequest].startAsync) + } + + // + // shoot the message to the root endpoint for processing + // IMPORTANT: the suspend method is invoked on the jetty thread not in the actor + // + val msg = builder(suspend _) + if (msg.context ne None) {_root ! msg} + } + + /** + * Subclasses can choose to have the servlet listen to the async context events + * @return A type of either AsyncListener or ContinuationListener + */ + def hook:Option[AnyRef] = None + + + // + // HttpServlet API + // + + final val Jetty7Server = "Jetty(7" + + override def init(config: ServletConfig) = + { + super.init(config) + + val context = config.getServletContext + val server = context.getServerInfo + val (major, minor) = (context.getMajorVersion, context.getMinorVersion) + + log.debug("Initializing Akka HTTP on "+server+" with Servlet API "+major+"."+minor) + + (major, minor) match { + + case (3,0) => { + log.debug("Supporting Java asynchronous contexts.") + } + + case (2,5) if (server startsWith Jetty7Server) => { + log.debug("Supporting Jetty asynchronous continuations.") + + } + + case _ => { + log.error("No asynchronous request handling can be supported.") + } + } + } + + + protected override def doDelete(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Delete(f, hook _)) + protected override def doGet(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Get(f, hook _)) + protected override def doHead(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Head(f, hook _)) + protected override def doOptions(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Options(f, hook _)) + protected override def doPost(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Post(f, hook _)) + protected override def doPut(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Put(f, hook _)) + protected override def doTrace(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Trace(f, hook _)) +} + diff --git a/akka-http/src/main/scala/Endpoint.scala b/akka-http/src/main/scala/Endpoint.scala new file mode 100644 index 0000000000..dd00ee4bd9 --- /dev/null +++ b/akka-http/src/main/scala/Endpoint.scala @@ -0,0 +1,134 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + +import javax.servlet.http.{HttpServletResponse, HttpServletRequest} +import akka.actor.{ActorRegistry, ActorRef, Actor} + +/** + * @author Garrick Evans + */ +trait Endpoint +{ + this: Actor => + + import Endpoint._ + + type Hook = Function[String, Boolean] + type Provider = Function[String, ActorRef] + + /** + * A convenience method to get the actor ref + */ + def actor: ActorRef = this.self + + /** + * The list of connected endpoints to which this one should/could forward the request. + * If the hook func returns true, the message will be sent to the actor returned from provider. + */ + protected var _attachments = List[Tuple2[Hook, Provider]]() + + /** + * + */ + protected def _attach(hook:Hook, provider:Provider) = + { + _attachments = (hook, provider) :: _attachments + } + + /** + * Message handling common to all endpoints, must be chained + */ + protected def _recv: Receive = + { + // + // add the endpoint - the if the uri hook matches, + // the message will be sent to the actor returned by the provider func + // + case Attach(hook, provider) => _attach(hook, provider) + + + // + // dispatch the suspended requests + // + case msg if msg.isInstanceOf[SuspendedRequest] => + { + val req = msg.asInstanceOf[SuspendedRequest] + val uri = req.request.getRequestURI + val endpoints = _attachments.filter {_._1(uri)} + + if (endpoints.size > 0) + endpoints.foreach {_._2(uri) ! req} + else + { + self.sender match + { + case Some(s) => s reply NoneAvailable(uri, req) + case None => _na(uri, req) + } + } + } + + } + + /** + * no endpoint available - completes the request with a 404 + */ + protected def _na(uri: String, req: SuspendedRequest) = + { + req.NotFound("No endpoint available for [" + uri + "]") + log.debug("No endpoint available for [" + uri + "]") + } +} + + +class RootEndpoint extends Actor with Endpoint +{ + import Endpoint._ + + final val Root = "/" + + // + // use the configurable dispatcher + // + self.dispatcher = Endpoint.Dispatcher + + override def preStart = _attachments = Tuple2((uri: String) => {uri eq Root}, (uri: String) => this.actor) :: _attachments + + def recv: Receive = + { + case NoneAvailable(uri, req) => _na(uri, req) + case unknown => + { + log.error("Unexpected message sent to root endpoint. [" + unknown.toString + "]") + } + } + + /** + * Note that root is a little different, other endpoints should chain their own recv first + */ + def receive = {_recv orElse recv} +} + + + +object Endpoint +{ + import akka.dispatch.Dispatchers + + + /** + * leverage the akka config to tweak the dispatcher for our endpoints + */ + final val Dispatcher = Dispatchers.fromConfig("akka.rest.comet-dispatcher") + + type Hook = Function[String, Boolean] + type Provider = Function[String, ActorRef] + + case class Attach(hook: Hook, provider: Provider) + case class NoneAvailable(uri: String, req: SuspendedRequest) +} diff --git a/akka-http/src/main/scala/SuspendedRequest.scala b/akka-http/src/main/scala/SuspendedRequest.scala new file mode 100644 index 0000000000..2f3ecf7912 --- /dev/null +++ b/akka-http/src/main/scala/SuspendedRequest.scala @@ -0,0 +1,252 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + + +import akka.util.Logging +import Types._ + + +/** + * @author Garrick Evans + */ +trait SuspendedRequest extends Logging +{ + import javax.servlet.http.{HttpServletResponse, HttpServletRequest} + import org.eclipse.jetty.server._ + import org.eclipse.jetty.continuation._ + + final val Timeout = "timeout" + final val DefaultTimeout = 30000 + + var context: Option[tAsyncContext] = None + + def init(suspend:()=>Option[tAsyncContext], callback:()=>Option[AnyRef]) = + { + suspend() match + { + case (Some(continuation)) => + { + context = Some(continuation) + val ac = continuation.asInstanceOf[AsyncContinuation] + + (ac.isInitial, ac.isSuspended, ac.isExpired) match + { + // + // the fresh continuation + // + case (true, false, false) => + { + ac.setTimeout(DefaultTimeout) + + //callback() foreach {listener => ac.addContinuationListener(listener)} + //ac.addContinuationListener(this) + ac.suspend + } + // + // the timeout was reset and the continuation was resumed + // need to update the timeout and resuspend + // very important to clear the context so the request is not rebroadcast to the endpoint + // + case (false, false, false) => + { + ac.setTimeout(ac.getAttribute(Timeout).asInstanceOf[Long]) + ac.suspend + ac.removeAttribute(Timeout) + + context = None + log.debug("Updating and re-suspending request. TIMEOUT ("+ac.getTimeout+" ms)") + } + // + // we don't actually expect to get this one here since the listener will finish him off + // + case (_, _, true) => + { + response.setStatus(HttpServletResponse.SC_REQUEST_TIMEOUT) + context = None + log.warning("Expired request arrived here unexpectedly. REQUEST ("+continuation.toString+")") + } + case unknown => + { + log.error("Unexpected continuation state detected - cancelling") + ac.cancel + context = None + } + } + } + case _ => + { + log.error("Cannot initialize request without an asynchronous context.") + } + } + } + + def request = context.get.getRequest.asInstanceOf[HttpServletRequest] + def response = context.get.getResponse.asInstanceOf[HttpServletResponse] + def suspended = + { + context match + { + case Some(continuation) => + { + val ac = continuation.asInstanceOf[AsyncContinuation] + (ac.isSuspended || (ac.getAttribute(Timeout) != null)) + } + case None => false + } + } + + def getHeaderOrElse(name: String, default: Function[Any, String]): String = + { + request.getHeader(name) match + { + case null => default(null) + case s => s + } + } + + def getParameterOrElse(name: String, default: Function[Any, String]): String = + { + request.getParameter(name) match + { + case null => default(null) + case s => s + } + } + + /** + * Allow for an updatable timeout + */ + def timeout(ms:Long):Unit = + { + context match + { + case Some(continuation) => + { + continuation.asInstanceOf[AsyncContinuation].setAttribute(Timeout, ms) + continuation.asInstanceOf[AsyncContinuation].resume + } + case None => log.error("Cannot update the timeout on an unsuspended request") + } + } + + def complete(status: Int, body: String): Boolean = complete(status, body, List[Tuple2[String, String]]()) + + def complete(status: Int, body: String, headers: List[Tuple2[String, String]]): Boolean = + { + var ok = false + context match + { + case Some(pipe) => + { + try + { + if (!suspended) + { + log.warning("Attempt to complete an expired connection.") + } + else + { + response.setStatus(status) + headers foreach {h => response.setHeader(h._1, h._2)} + response.getWriter.write(body) + response.getWriter.close + response.flushBuffer + pipe.complete + ok = true + } + } + catch + { + case ex => log.error(ex, "Failed to write data to connection on resume - the client probably disconnected") + } + finally + { + context = None + } + } + case None => + { + log.error("Attempt to complete request with no context. STATUS (" + status + ") BODY (" + body + ") HEADERS (" + headers + ")") + } + } + ok + } + + def complete(t: Throwable): Unit = + { + var status = 0 + context match + { + case Some(pipe) => + { + try + { + if (!suspended) + { + log.warning("Attempt to complete an expired connection.") + } + else + { + status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR + response.sendError(status, "Failed to write data to connection on resume") + pipe.complete + } + } + catch + { + case ex => log.error(ex, "Request completed with internal error.") + } + finally + { + context = None + log.error(t, "Request completed with internal error.") + } + } + case None => + { + log.error(t, "Attempt to complete request with no context") + } + } + } + + def onComplete(c:Continuation) = {} + def onTimeout(c:Continuation) = + { + c.getServletResponse.asInstanceOf[HttpServletResponse].addHeader("Suspend","Timeout") + c.complete + log.debug("Request expired. CONTEXT ("+c+")") + } + + + def OK(body: String): Boolean = complete(HttpServletResponse.SC_OK, body) + def OK(body: String, headers:List[Tuple2[String,String]]): Boolean = complete(HttpServletResponse.SC_OK, body, headers) + def Created(body: String): Boolean = complete(HttpServletResponse.SC_CREATED, body) + def Accepted(body: String): Boolean = complete(HttpServletResponse.SC_ACCEPTED, body) + def NotModified(body:String): Boolean = complete(HttpServletResponse.SC_NOT_MODIFIED, body) + def BadRequest(body: String): Boolean = complete(HttpServletResponse.SC_BAD_REQUEST, body) + def Unauthorized(body: String): Boolean = complete(HttpServletResponse.SC_UNAUTHORIZED, body) + def Forbidden(body: String): Boolean = complete(HttpServletResponse.SC_FORBIDDEN, body) + def NotAllowed(body: String): Boolean = complete(HttpServletResponse.SC_METHOD_NOT_ALLOWED, body) + def NotFound(body: String): Boolean = complete(HttpServletResponse.SC_NOT_FOUND, body) + def Timeout(body: String): Boolean = complete(HttpServletResponse.SC_REQUEST_TIMEOUT, body) + def Conflict(body: String): Boolean = complete(HttpServletResponse.SC_CONFLICT, body) + def UnsupportedMediaType(body: String): Boolean = complete(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, body) + def Error(body: String): Boolean = complete(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, body) + def NotImplemented(body: String): Boolean = complete(HttpServletResponse.SC_NOT_IMPLEMENTED, body) + def Unavailable(body: String, retry: Int): Boolean = complete(HttpServletResponse.SC_SERVICE_UNAVAILABLE, body, List(("Retry-After", retry.toString))) +} + + +case class Delete(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Get(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Head(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Options(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Post(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Put(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} +case class Trace(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} + diff --git a/akka-http/src/main/scala/Types.scala b/akka-http/src/main/scala/Types.scala new file mode 100644 index 0000000000..ba423194a3 --- /dev/null +++ b/akka-http/src/main/scala/Types.scala @@ -0,0 +1,50 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + + +/** + * Structural type alias's required to work with both Servlet 3.0 and Jetty's Continuation API + * + * @author Garrick Evans + */ +object Types +{ + import javax.servlet. {ServletContext, ServletRequest, ServletResponse} + + type tAsyncRequest = { + def startAsync:tAsyncContext + } + + type tAsyncContext = { + def complete:Unit + def dispatch:Unit + def dispatch(s:String):Unit + def dispatch(c:ServletContext, s:String) + def getRequest:ServletRequest + def getResponse:ServletResponse + def hasOriginalRequestAndResponse:Boolean + def setTimeout(ms:Long):Unit + def start(r:Runnable):Unit + } + + type tContinuation = { + def complete:Unit + def isExpired:Boolean + def isInitial:Boolean + def isResumed:Boolean + def isSuspended:Boolean + def resume:Unit + def suspend:Unit + def undispatch:Unit + } + + type tContinuationListener = { + def onComplete(c:tContinuation) + def onTimeout(c:tContinuation) + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala new file mode 100644 index 0000000000..38087b03db --- /dev/null +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala @@ -0,0 +1,45 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package sample.mist + +import akka.actor._ +import akka.actor.Actor._ +import akka.config.Supervision._ +import akka.http._ + + +/** + * Starts up the base services for http (jetty) + */ +class Boot { + val factory = SupervisorFactory( + SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + actorOf[ServiceRoot], + Permanent) :: + Supervise( + actorOf[SimpleService], + Permanent) + :: Nil)) + factory.newInstance.start +} + + +class ServiceRoot extends RootEndpoint +{ + // + // use the configurable dispatcher + // + self.dispatcher = Endpoint.Dispatcher + + // + // TODO: make this a config prop + // + self.id = "DefaultRootEndpoint" +} + diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala new file mode 100644 index 0000000000..333cdb3c86 --- /dev/null +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala @@ -0,0 +1,155 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package sample.mist + +import akka.actor._ +import akka.actor.Actor._ +import akka.http._ + + + +/** + * Define a top level service endpoint + * Usage: GET or POST to http://localhost:9998/simple/same or http://localhost:9998/simple/new + * + * @author Garrick Evans + */ +class SimpleService extends Actor with Endpoint +{ + final val ServiceRoot = "/simple/" + final val ProvideSameActor = ServiceRoot + "same" + final val ProvideNewActor = ServiceRoot + "new" + + // + // use the configurable dispatcher + // + self.dispatcher = Endpoint.Dispatcher + + // + // there are different ways of doing this - in this case, we'll use a single hook function + // and discriminate in the provider; alternatively we can pair hooks & providers + // + def hook(uri: String): Boolean = ((uri == ProvideSameActor) || (uri == ProvideNewActor)) + def provide(uri: String): ActorRef = + { + if (uri == ProvideSameActor) + same + else + actorOf[BoringActor].start + } + + // + // this is where you want attach your endpoint hooks + // + override def preStart = + { + // + // we expect there to be one root and that it's already been started up + // obviously there are plenty of other ways to obtaining this actor + // the point is that we need to attach something (for starters anyway) + // to the root + // + val root = ActorRegistry.actorsFor(classOf[RootEndpoint]).head + root ! Endpoint.Attach(hook, provide) + } + + // + // since this actor isn't doing anything else (i.e. not handling other messages) + // just assign the receive func like so... + // otherwise you could do something like: + // def myrecv = {...} + // def receive = myrecv orElse _recv + // + def receive = _recv + + + // + // this will be our "same" actor provided with ProvideSameActor endpoint is hit + // + lazy val same = actorOf[BoringActor].start +} + +/** + * Define a service handler to respond to some HTTP requests + */ +class BoringActor extends Actor +{ + import java.util.Date + import javax.ws.rs.core.MediaType + + var gets = 0 + var posts = 0 + var lastget:Option[Date] = None + var lastpost:Option[Date] = None + + def receive = + { + // + // handle a get request + // + case get:Get => + { + // + // the content type of the response. + // similar to @Produces annotation + // + get.response.setContentType(MediaType.TEXT_HTML) + + // + // "work" + // + gets += 1 + lastget = Some(new Date) + + // + // respond + // + val res = "

Gets: "+gets+" Posts: "+posts+"

Last Get: "+lastget.getOrElse("Never").toString+" Last Post: "+lastpost.getOrElse("Never").toString+"

" + get.OK(res) + } + + // + // handle a post request + // + case post:Post => + { + // + // the expected content type of the request + // similar to @Consumes + // + if (post.request.getContentType startsWith MediaType.APPLICATION_FORM_URLENCODED) + { + // + // the content type of the response. + // similar to @Produces annotation + // + post.response.setContentType(MediaType.TEXT_HTML) + + // + // "work" + // + posts += 1 + lastpost = Some(new Date) + + // + // respond + // + val res = "

Gets: "+gets+" Posts: "+posts+"

Last Get: "+lastget.getOrElse("Never").toString+" Last Post: "+lastpost.getOrElse("Never").toString+"

" + post.OK(res) + } + else + { + post.UnsupportedMediaType("Content-Type request header missing or incorrect (was '" + post.request.getContentType + "' should be '" + MediaType.APPLICATION_FORM_URLENCODED + "')") + } + } + + case other if other.isInstanceOf[SuspendedRequest] => + { + other.asInstanceOf[SuspendedRequest].NotAllowed("Invalid method for this endpoint") + } + } +} From 5ff7b8517fab207ef75866d1dc991ac29a4c1fbe Mon Sep 17 00:00:00 2001 From: Garrick Evans Date: Mon, 8 Nov 2010 16:15:19 -0800 Subject: [PATCH 02/17] refactoring WIP - doesn't build; added servlet 3.0 api jar from glassfish to proj dep --- akka-http/src/main/scala/ContinuationSupport.scala | 0 project/build/AkkaProject.scala | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 akka-http/src/main/scala/ContinuationSupport.scala diff --git a/akka-http/src/main/scala/ContinuationSupport.scala b/akka-http/src/main/scala/ContinuationSupport.scala new file mode 100644 index 0000000000..e69de29bb2 diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 79afea0a44..57510103d3 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -72,6 +72,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val CodehausRepo = MavenRepository("Codehaus Repo", "http://repository.codehaus.org") lazy val EmbeddedRepo = MavenRepository("Embedded Repo", (info.projectPath / "embedded-repo").asURL.toString) lazy val FusesourceSnapshotRepo = MavenRepository("Fusesource Snapshots", "http://repo.fusesource.com/nexus/content/repositories/snapshots") + lazy val GlassfishRepo = MavenRepository("Glassfish Repo", "http://download.java.net/maven/glassfish") lazy val GuiceyFruitRepo = MavenRepository("GuiceyFruit Repo", "http://guiceyfruit.googlecode.com/svn/repo/releases/") lazy val JBossRepo = MavenRepository("JBoss Repo", "http://repository.jboss.org/nexus/content/groups/public/") lazy val JavaNetRepo = MavenRepository("java.net Repo", "http://download.java.net/maven/2") @@ -112,6 +113,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val casbahModuleConfig = ModuleConfiguration("com.novus", CasbahRepo) lazy val timeModuleConfig = ModuleConfiguration("org.scala-tools", "time", CasbahSnapshotRepo) lazy val voldemortModuleConfig = ModuleConfiguration("voldemort", ClojarsRepo) + lazy val glassfishModuleConfig = ModuleConfiguration("org.glassfish", GlassfishRepo) lazy val embeddedRepo = EmbeddedRepo // This is the only exception, because the embedded repo is fast! // ------------------------------------------------------------------------------------------------------------------- @@ -132,6 +134,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val SPRING_VERSION = "3.0.4.RELEASE" lazy val ASPECTWERKZ_VERSION = "2.2.2" lazy val JETTY_VERSION = "7.1.6.v20100715" + lazy val JAVAX_SERVLET_VERSION = "3.0" // ------------------------------------------------------------------------------------------------------------------- // Dependencies @@ -170,6 +173,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val dispatch_http = "net.databinder" % "dispatch-http_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 lazy val dispatch_json = "net.databinder" % "dispatch-json_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 + lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "compile" //CDDL v1 + lazy val jetty = "org.eclipse.jetty" % "jetty-server" % JETTY_VERSION % "compile" //Eclipse license lazy val jetty_util = "org.eclipse.jetty" % "jetty-util" % JETTY_VERSION % "compile" //Eclipse license lazy val jetty_xml = "org.eclipse.jetty" % "jetty-xml" % JETTY_VERSION % "compile" //Eclipse license @@ -504,6 +509,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val jersey_server = Dependencies.jersey_server val jsr311 = Dependencies.jsr311 val stax_api = Dependencies.stax_api + val servlet30 = Dependencies.javax_servlet_30 // testing val junit = Dependencies.junit From 92df463e9267b0b9e49aceef673bc5da8762ef74 Mon Sep 17 00:00:00 2001 From: Garrick Evans Date: Tue, 9 Nov 2010 23:02:59 -0800 Subject: [PATCH 03/17] most of the refactoring done and jetty is working again (need to check updating timeouts, etc); servlet 3.0 impl next --- .../src/main/scala/AkkaHttpServlet.scala | 130 +++++++++ .../src/main/scala/AsyncHttpServlet.scala | 109 -------- .../src/main/scala/ContinuationSupport.scala | 0 akka-http/src/main/scala/Endpoint.scala | 14 +- .../src/main/scala/JettyContinuation.scala | 140 ++++++++++ akka-http/src/main/scala/RequestMethod.scala | 186 +++++++++++++ .../src/main/scala/SuspendedRequest.scala | 252 ------------------ akka-http/src/main/scala/Types.scala | 31 +-- .../src/main/scala/Boot.scala | 23 +- ...scala => SimpleAkkaAsyncHttpService.scala} | 8 +- .../src/main/scala/SimpleService.scala | 2 +- config/akka-reference.conf | 7 + config/microkernel-server.xml | 2 +- 13 files changed, 491 insertions(+), 413 deletions(-) create mode 100644 akka-http/src/main/scala/AkkaHttpServlet.scala delete mode 100644 akka-http/src/main/scala/AsyncHttpServlet.scala delete mode 100644 akka-http/src/main/scala/ContinuationSupport.scala create mode 100644 akka-http/src/main/scala/JettyContinuation.scala create mode 100644 akka-http/src/main/scala/RequestMethod.scala delete mode 100644 akka-http/src/main/scala/SuspendedRequest.scala rename akka-samples/akka-sample-rest-scala/src/main/scala/{SimpleService2.scala => SimpleAkkaAsyncHttpService.scala} (94%) diff --git a/akka-http/src/main/scala/AkkaHttpServlet.scala b/akka-http/src/main/scala/AkkaHttpServlet.scala new file mode 100644 index 0000000000..61f2070ab0 --- /dev/null +++ b/akka-http/src/main/scala/AkkaHttpServlet.scala @@ -0,0 +1,130 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + +import akka.util.Logging +import javax.servlet.http.HttpServlet + +/** + * @author Garrick Evans + */ +class AkkaHttpServlet extends HttpServlet with Logging +{ + import java.util. {Date, TimeZone} + import java.text.SimpleDateFormat + import javax.servlet.ServletConfig + import javax.servlet.http.{HttpServletRequest, HttpServletResponse} + import akka.actor.ActorRegistry + import Types._ + import AkkaHttpServlet._ + + + /** + * The root endpoint actor + */ + protected val _root = ActorRegistry.actorsFor(RootActorID).head + + /** + * Server-specific method factory + */ + protected var _factory:Option[RequestMethodFactory] = None + + /** + * Handles all servlet requests + */ + protected def _do(request:HttpServletRequest, response:HttpServletResponse)(builder:(()=>tAsyncRequestContext)=>RequestMethod) = + { + def suspend:tAsyncRequestContext = + { + // + // set to right now, which is effectively "already expired" + // + val gmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z") + gmt.setTimeZone(TimeZone.getTimeZone("GMT")) + + response.setHeader("Expires", gmt.format(new Date)) + response.setHeader("Cache-Control", "no-cache, must-revalidate") + + // + // no keep-alive? + // + if (ConnectionClose) response.setHeader("Connection","close") + + // + // suspend the request + // TODO: move this out to the specialized support if jetty asyncstart doesnt let us update TOs + // + request.asInstanceOf[tAsyncRequest].startAsync.asInstanceOf[tAsyncRequestContext] + } + + // + // shoot the message to the root endpoint for processing + // IMPORTANT: the suspend method is invoked on the server thread not in the actor + // + val method = builder(suspend _) + if (method.go) {_root ! method} + } + + + // + // HttpServlet API + // + + override def init(config: ServletConfig) = + { + super.init(config) + + val context = config.getServletContext + val server = context.getServerInfo + val (major, minor) = (context.getMajorVersion, context.getMinorVersion) + + log.info("Initializing Akka HTTP on "+server+" with Servlet API "+major+"."+minor) + + (major, minor) match { + + case (3,0) => { + log.info("Supporting Java asynchronous contexts.") + } + + case _ if (server.toLowerCase startsWith JettyServer) => { + + log.info("Supporting Jetty asynchronous continuations.") + _factory = Some(JettyContinuationMethodFactory) + } + + case _ => { + log.error("No asynchronous request handling can be supported.") + } + } + } + + protected override def doDelete(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Delete) + protected override def doGet(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Get) + protected override def doHead(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Head) + protected override def doOptions(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Options) + protected override def doPost(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Post) + protected override def doPut(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)(_factory.get.Put) + protected override def doTrace(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response) (_factory.get.Trace) +} + +object AkkaHttpServlet +{ + import akka.config.Config._ + + final val JettyServer = "jetty" + final val TimeoutAttribute = "timeout" + + val ConnectionClose = config.getBool("akka.rest.connection-close", true) + val RootActorBuiltin = config.getBool("akka.rest.root-actor-builtin", true) + val RootActorID = config.getString("akka.rest.root-actor-id", "_httproot") + val DefaultTimeout = config.getLong("akka.rest.timeout", 1000) + val ExpiredHeaderName = config.getString("akka.rest.expired-header-name", "Async-Timeout") + val ExpiredHeaderValue = config.getString("akka.rest.expired-header-value", "expired") +} + + + diff --git a/akka-http/src/main/scala/AsyncHttpServlet.scala b/akka-http/src/main/scala/AsyncHttpServlet.scala deleted file mode 100644 index 7e9ccb4855..0000000000 --- a/akka-http/src/main/scala/AsyncHttpServlet.scala +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright 2010 Autodesk, Inc. All rights reserved. - * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. - */ - -package akka.http - -import akka.util.Logging -import javax.servlet.http.{HttpServletResponse, HttpServlet} - -/** - * @author Garrick Evans - */ -class AsyncHttpServlet extends HttpServlet with Logging -{ - import java.util. {Date, TimeZone} - import java.text.SimpleDateFormat - import javax.servlet.ServletConfig - import javax.servlet.http.{HttpServletRequest, HttpServletResponse} - import akka.actor.ActorRegistry - import Types._ - - // - // the root endpoint for this servlet will have been booted already - // use the system property to find out the actor id and cache him - // TODO: currently this is hardcoded but really use a property - // - protected val _root = ActorRegistry.actorsFor("DefaultGridRoot").head - - /** - * Handles the HTTP request method on the servlet, suspends the connection and sends the asynchronous context - * along to the root endpoint in a SuspendedRequest message - */ - protected def _do(request:HttpServletRequest, response:HttpServletResponse)(builder: (()=>Option[tAsyncContext]) => SuspendedRequest) = - { - def suspend:Option[tAsyncContext] = - { - // - // set to effectively "already expired" - // - val gmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z") - gmt.setTimeZone(TimeZone.getTimeZone("GMT")) - - response.setHeader("Expires", gmt.format(new Date)) - response.setHeader("Cache-Control", "no-cache, must-revalidate") - response.setHeader("Connection","close") - - Some(request.asInstanceOf[tAsyncRequest].startAsync) - } - - // - // shoot the message to the root endpoint for processing - // IMPORTANT: the suspend method is invoked on the jetty thread not in the actor - // - val msg = builder(suspend _) - if (msg.context ne None) {_root ! msg} - } - - /** - * Subclasses can choose to have the servlet listen to the async context events - * @return A type of either AsyncListener or ContinuationListener - */ - def hook:Option[AnyRef] = None - - - // - // HttpServlet API - // - - final val Jetty7Server = "Jetty(7" - - override def init(config: ServletConfig) = - { - super.init(config) - - val context = config.getServletContext - val server = context.getServerInfo - val (major, minor) = (context.getMajorVersion, context.getMinorVersion) - - log.debug("Initializing Akka HTTP on "+server+" with Servlet API "+major+"."+minor) - - (major, minor) match { - - case (3,0) => { - log.debug("Supporting Java asynchronous contexts.") - } - - case (2,5) if (server startsWith Jetty7Server) => { - log.debug("Supporting Jetty asynchronous continuations.") - - } - - case _ => { - log.error("No asynchronous request handling can be supported.") - } - } - } - - - protected override def doDelete(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Delete(f, hook _)) - protected override def doGet(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Get(f, hook _)) - protected override def doHead(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Head(f, hook _)) - protected override def doOptions(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Options(f, hook _)) - protected override def doPost(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Post(f, hook _)) - protected override def doPut(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Put(f, hook _)) - protected override def doTrace(request: HttpServletRequest, response: HttpServletResponse) = _do(request, response)((f:(()=>Option[tAsyncContext])) => Trace(f, hook _)) -} - diff --git a/akka-http/src/main/scala/ContinuationSupport.scala b/akka-http/src/main/scala/ContinuationSupport.scala deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/akka-http/src/main/scala/Endpoint.scala b/akka-http/src/main/scala/Endpoint.scala index dd00ee4bd9..42c6d02758 100644 --- a/akka-http/src/main/scala/Endpoint.scala +++ b/akka-http/src/main/scala/Endpoint.scala @@ -55,9 +55,9 @@ trait Endpoint // // dispatch the suspended requests // - case msg if msg.isInstanceOf[SuspendedRequest] => + case msg if msg.isInstanceOf[RequestMethod] => { - val req = msg.asInstanceOf[SuspendedRequest] + val req = msg.asInstanceOf[RequestMethod] val uri = req.request.getRequestURI val endpoints = _attachments.filter {_._1(uri)} @@ -78,7 +78,7 @@ trait Endpoint /** * no endpoint available - completes the request with a 404 */ - protected def _na(uri: String, req: SuspendedRequest) = + protected def _na(uri: String, req: RequestMethod) = { req.NotFound("No endpoint available for [" + uri + "]") log.debug("No endpoint available for [" + uri + "]") @@ -89,6 +89,7 @@ trait Endpoint class RootEndpoint extends Actor with Endpoint { import Endpoint._ + import AkkaHttpServlet._ final val Root = "/" @@ -97,6 +98,11 @@ class RootEndpoint extends Actor with Endpoint // self.dispatcher = Endpoint.Dispatcher + // + // adopt the configured id + // + if (RootActorBuiltin) self.id = RootActorID + override def preStart = _attachments = Tuple2((uri: String) => {uri eq Root}, (uri: String) => this.actor) :: _attachments def recv: Receive = @@ -130,5 +136,5 @@ object Endpoint type Provider = Function[String, ActorRef] case class Attach(hook: Hook, provider: Provider) - case class NoneAvailable(uri: String, req: SuspendedRequest) + case class NoneAvailable(uri: String, req: RequestMethod) } diff --git a/akka-http/src/main/scala/JettyContinuation.scala b/akka-http/src/main/scala/JettyContinuation.scala new file mode 100644 index 0000000000..6c753382f2 --- /dev/null +++ b/akka-http/src/main/scala/JettyContinuation.scala @@ -0,0 +1,140 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + +import org.eclipse.jetty.server._ +import org.eclipse.jetty.continuation._ +import Types._ + + +/** + * @author Garrick Evans + */ +trait JettyContinuation extends ContinuationListener with akka.util.Logging +{ + import javax.servlet.http.HttpServletResponse + import AkkaHttpServlet._ + + val builder:()=>tAsyncRequestContext + val context:Option[tAsyncRequestContext] = Some(builder()) + def go = {_continuation.isDefined} + + protected val _continuation:Option[AsyncContinuation] = { + + val continuation = context.get.asInstanceOf[AsyncContinuation] + + (continuation.isInitial, + continuation.isSuspended, + continuation.isExpired) match { + + // + // the fresh continuation (coming through getAsyncContinuation) + // + case (true, false, false) => { + + continuation.setTimeout(DefaultTimeout) + + //callback() foreach {listener => continuation.addContinuationListener(listener.asInstanceOf[ContinuationListener])} + continuation.addContinuationListener(this) + continuation.suspend + + Some(continuation) + } + // + // the fresh continuation (coming through startAsync instead) + // + case (true, true, false) => { + + continuation.setTimeout(DefaultTimeout) + + //callback() foreach {listener => continuation.addContinuationListener(listener.asInstanceOf[ContinuationListener])} + continuation.addContinuationListener(this) + + Some(continuation) + } + // + // the timeout was reset and the continuation was resumed + // need to update the timeout and resuspend + // very important to clear the context so the request is not rebroadcast to the endpoint + // + case (false, false, false) => { + + continuation.setTimeout(continuation.getAttribute(TimeoutAttribute).asInstanceOf[Long]) + continuation.suspend + continuation.removeAttribute(TimeoutAttribute) + + None + } + // + // we don't actually expect to get this one here since the listener will finish him off + // + case (_, _, true) => { + + None + } + // + // snuh? + // + case _ => { + + continuation.cancel + + None + } + } + } + + def suspended:Boolean = + { + _continuation match { + + case Some(continuation) => (continuation.isSuspended || (continuation.getAttribute(TimeoutAttribute) != null)) + case None => { + false + } + } + } + + def timeout(ms:Long):Boolean = + { + _continuation match { + + case Some(continuation) => { + + continuation.setAttribute(TimeoutAttribute, ms) + continuation.resume + true + } + + case None => false + } + } + + // + // ContinuationListener + // + + def onComplete(c:Continuation) = {} + def onTimeout(c:Continuation) = + { + c.getServletResponse.asInstanceOf[HttpServletResponse].addHeader(ExpiredHeaderName, ExpiredHeaderValue) + c.complete + } +} + +object JettyContinuationMethodFactory extends RequestMethodFactory +{ + def Delete(f:(()=>tAsyncRequestContext)):RequestMethod = {new Delete(f) with JettyContinuation} + def Get(f:(()=>tAsyncRequestContext)):RequestMethod = {new Get(f) with JettyContinuation} + def Head(f:(()=>tAsyncRequestContext)):RequestMethod = {new Head(f) with JettyContinuation} + def Options(f:(()=>tAsyncRequestContext)):RequestMethod = {new Options(f) with JettyContinuation} + def Post(f:(()=>tAsyncRequestContext)):RequestMethod = {new Post(f) with JettyContinuation} + def Put(f:(()=>tAsyncRequestContext)):RequestMethod = {new Put(f) with JettyContinuation} + def Trace(f:(()=>tAsyncRequestContext)):RequestMethod = {new Trace(f) with JettyContinuation} +} + + diff --git a/akka-http/src/main/scala/RequestMethod.scala b/akka-http/src/main/scala/RequestMethod.scala new file mode 100644 index 0000000000..ba86336521 --- /dev/null +++ b/akka-http/src/main/scala/RequestMethod.scala @@ -0,0 +1,186 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + + +import akka.util.Logging +import Types._ + + +/** + * Basic description of the suspended async http request. + * Must be mixed with some kind of specific support (e.g. servlet 3.0 or jetty continuations) + * + * @author Garrick Evans + */ +trait RequestMethod extends Logging +{ + import java.io.IOException + import javax.servlet.http.{HttpServletResponse, HttpServletRequest} + + // + // required implementations + // + + val builder:()=>tAsyncRequestContext + + /** + * Provides a general type for the underlying context + * + * @return a completable request context + */ + val context:Option[tAsyncRequestContext] + def go:Boolean + + /** + * Updates (resets) the timeout + * + * @return true if updated, false if not supported + */ + def timeout(ms:Long):Boolean + + /** + * Status of the suspension + */ + def suspended:Boolean + + // + // convenience funcs + // + + def request = context.get.getRequest.asInstanceOf[HttpServletRequest] + def response = context.get.getResponse.asInstanceOf[HttpServletResponse] + + def getHeaderOrElse(name: String, default: Function[Any, String]): String = + { + request.getHeader(name) match { + + case null => default(null) + case s => s + } + } + + def getParameterOrElse(name: String, default: Function[Any, String]): String = + { + request.getParameter(name) match { + + case null => default(null) + case s => s + } + } + + + def complete(status: Int, body: String): Boolean = complete(status, body, List[Tuple2[String, String]]()) + + def complete(status: Int, body: String, headers: List[Tuple2[String, String]]): Boolean = + { + var ok = false + context match { + + case Some(pipe) => { + try + { + if (!suspended) + { + log.warning("Attempt to complete an expired connection.") + } + else + { + response.setStatus(status) + headers foreach {h => response.setHeader(h._1, h._2)} + response.getWriter.write(body) + response.getWriter.close + response.flushBuffer + pipe.complete + ok = true + } + } + catch + { + case io:IOException => log.error(io, "Failed to write data to connection on resume - the client probably disconnected") + } + } + + case None => { + log.error("Attempt to complete request with no context. STATUS (" + status + ") BODY (" + body + ") HEADERS (" + headers + ")") + } + } + ok +} + + def complete(t: Throwable): Unit = + { + var status = 0 + context match { + + case Some(pipe) => { + try + { + if (!suspended) + { + log.warning("Attempt to complete an expired connection.") + } + else + { + status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR + response.sendError(status, "Failed to write data to connection on resume") + pipe.complete + } + } + catch + { + case io:IOException => log.error(io, "Request completed with internal error.") + } + finally + { + log.error(t, "Request completed with internal error.") + } + } + + case None => { + log.error(t, "Attempt to complete request with no context") + } + } + } + + + def OK(body: String): Boolean = complete(HttpServletResponse.SC_OK, body) + def OK(body: String, headers:List[Tuple2[String,String]]): Boolean = complete(HttpServletResponse.SC_OK, body, headers) + def Created(body: String): Boolean = complete(HttpServletResponse.SC_CREATED, body) + def Accepted(body: String): Boolean = complete(HttpServletResponse.SC_ACCEPTED, body) + def NotModified(body:String): Boolean = complete(HttpServletResponse.SC_NOT_MODIFIED, body) + def BadRequest(body: String): Boolean = complete(HttpServletResponse.SC_BAD_REQUEST, body) + def Unauthorized(body: String): Boolean = complete(HttpServletResponse.SC_UNAUTHORIZED, body) + def Forbidden(body: String): Boolean = complete(HttpServletResponse.SC_FORBIDDEN, body) + def NotAllowed(body: String): Boolean = complete(HttpServletResponse.SC_METHOD_NOT_ALLOWED, body) + def NotFound(body: String): Boolean = complete(HttpServletResponse.SC_NOT_FOUND, body) + def Timeout(body: String): Boolean = complete(HttpServletResponse.SC_REQUEST_TIMEOUT, body) + def Conflict(body: String): Boolean = complete(HttpServletResponse.SC_CONFLICT, body) + def UnsupportedMediaType(body: String): Boolean = complete(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, body) + def Error(body: String): Boolean = complete(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, body) + def NotImplemented(body: String): Boolean = complete(HttpServletResponse.SC_NOT_IMPLEMENTED, body) + def Unavailable(body: String, retry: Int): Boolean = complete(HttpServletResponse.SC_SERVICE_UNAVAILABLE, body, List(("Retry-After", retry.toString))) +} + +abstract class Delete(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Get(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Head(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Options(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Post(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Put(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} +abstract class Trace(f:(()=>tAsyncRequestContext)) extends RequestMethod {val builder = f} + +trait RequestMethodFactory +{ + def Delete(f:(()=>tAsyncRequestContext)):RequestMethod + def Get(f:(()=>tAsyncRequestContext)):RequestMethod + def Head(f:(()=>tAsyncRequestContext)):RequestMethod + def Options(f:(()=>tAsyncRequestContext)):RequestMethod + def Post(f:(()=>tAsyncRequestContext)):RequestMethod + def Put(f:(()=>tAsyncRequestContext)):RequestMethod + def Trace(f:(()=>tAsyncRequestContext)):RequestMethod +} diff --git a/akka-http/src/main/scala/SuspendedRequest.scala b/akka-http/src/main/scala/SuspendedRequest.scala deleted file mode 100644 index 2f3ecf7912..0000000000 --- a/akka-http/src/main/scala/SuspendedRequest.scala +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright 2010 Autodesk, Inc. All rights reserved. - * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. - */ - -package akka.http - - -import akka.util.Logging -import Types._ - - -/** - * @author Garrick Evans - */ -trait SuspendedRequest extends Logging -{ - import javax.servlet.http.{HttpServletResponse, HttpServletRequest} - import org.eclipse.jetty.server._ - import org.eclipse.jetty.continuation._ - - final val Timeout = "timeout" - final val DefaultTimeout = 30000 - - var context: Option[tAsyncContext] = None - - def init(suspend:()=>Option[tAsyncContext], callback:()=>Option[AnyRef]) = - { - suspend() match - { - case (Some(continuation)) => - { - context = Some(continuation) - val ac = continuation.asInstanceOf[AsyncContinuation] - - (ac.isInitial, ac.isSuspended, ac.isExpired) match - { - // - // the fresh continuation - // - case (true, false, false) => - { - ac.setTimeout(DefaultTimeout) - - //callback() foreach {listener => ac.addContinuationListener(listener)} - //ac.addContinuationListener(this) - ac.suspend - } - // - // the timeout was reset and the continuation was resumed - // need to update the timeout and resuspend - // very important to clear the context so the request is not rebroadcast to the endpoint - // - case (false, false, false) => - { - ac.setTimeout(ac.getAttribute(Timeout).asInstanceOf[Long]) - ac.suspend - ac.removeAttribute(Timeout) - - context = None - log.debug("Updating and re-suspending request. TIMEOUT ("+ac.getTimeout+" ms)") - } - // - // we don't actually expect to get this one here since the listener will finish him off - // - case (_, _, true) => - { - response.setStatus(HttpServletResponse.SC_REQUEST_TIMEOUT) - context = None - log.warning("Expired request arrived here unexpectedly. REQUEST ("+continuation.toString+")") - } - case unknown => - { - log.error("Unexpected continuation state detected - cancelling") - ac.cancel - context = None - } - } - } - case _ => - { - log.error("Cannot initialize request without an asynchronous context.") - } - } - } - - def request = context.get.getRequest.asInstanceOf[HttpServletRequest] - def response = context.get.getResponse.asInstanceOf[HttpServletResponse] - def suspended = - { - context match - { - case Some(continuation) => - { - val ac = continuation.asInstanceOf[AsyncContinuation] - (ac.isSuspended || (ac.getAttribute(Timeout) != null)) - } - case None => false - } - } - - def getHeaderOrElse(name: String, default: Function[Any, String]): String = - { - request.getHeader(name) match - { - case null => default(null) - case s => s - } - } - - def getParameterOrElse(name: String, default: Function[Any, String]): String = - { - request.getParameter(name) match - { - case null => default(null) - case s => s - } - } - - /** - * Allow for an updatable timeout - */ - def timeout(ms:Long):Unit = - { - context match - { - case Some(continuation) => - { - continuation.asInstanceOf[AsyncContinuation].setAttribute(Timeout, ms) - continuation.asInstanceOf[AsyncContinuation].resume - } - case None => log.error("Cannot update the timeout on an unsuspended request") - } - } - - def complete(status: Int, body: String): Boolean = complete(status, body, List[Tuple2[String, String]]()) - - def complete(status: Int, body: String, headers: List[Tuple2[String, String]]): Boolean = - { - var ok = false - context match - { - case Some(pipe) => - { - try - { - if (!suspended) - { - log.warning("Attempt to complete an expired connection.") - } - else - { - response.setStatus(status) - headers foreach {h => response.setHeader(h._1, h._2)} - response.getWriter.write(body) - response.getWriter.close - response.flushBuffer - pipe.complete - ok = true - } - } - catch - { - case ex => log.error(ex, "Failed to write data to connection on resume - the client probably disconnected") - } - finally - { - context = None - } - } - case None => - { - log.error("Attempt to complete request with no context. STATUS (" + status + ") BODY (" + body + ") HEADERS (" + headers + ")") - } - } - ok - } - - def complete(t: Throwable): Unit = - { - var status = 0 - context match - { - case Some(pipe) => - { - try - { - if (!suspended) - { - log.warning("Attempt to complete an expired connection.") - } - else - { - status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR - response.sendError(status, "Failed to write data to connection on resume") - pipe.complete - } - } - catch - { - case ex => log.error(ex, "Request completed with internal error.") - } - finally - { - context = None - log.error(t, "Request completed with internal error.") - } - } - case None => - { - log.error(t, "Attempt to complete request with no context") - } - } - } - - def onComplete(c:Continuation) = {} - def onTimeout(c:Continuation) = - { - c.getServletResponse.asInstanceOf[HttpServletResponse].addHeader("Suspend","Timeout") - c.complete - log.debug("Request expired. CONTEXT ("+c+")") - } - - - def OK(body: String): Boolean = complete(HttpServletResponse.SC_OK, body) - def OK(body: String, headers:List[Tuple2[String,String]]): Boolean = complete(HttpServletResponse.SC_OK, body, headers) - def Created(body: String): Boolean = complete(HttpServletResponse.SC_CREATED, body) - def Accepted(body: String): Boolean = complete(HttpServletResponse.SC_ACCEPTED, body) - def NotModified(body:String): Boolean = complete(HttpServletResponse.SC_NOT_MODIFIED, body) - def BadRequest(body: String): Boolean = complete(HttpServletResponse.SC_BAD_REQUEST, body) - def Unauthorized(body: String): Boolean = complete(HttpServletResponse.SC_UNAUTHORIZED, body) - def Forbidden(body: String): Boolean = complete(HttpServletResponse.SC_FORBIDDEN, body) - def NotAllowed(body: String): Boolean = complete(HttpServletResponse.SC_METHOD_NOT_ALLOWED, body) - def NotFound(body: String): Boolean = complete(HttpServletResponse.SC_NOT_FOUND, body) - def Timeout(body: String): Boolean = complete(HttpServletResponse.SC_REQUEST_TIMEOUT, body) - def Conflict(body: String): Boolean = complete(HttpServletResponse.SC_CONFLICT, body) - def UnsupportedMediaType(body: String): Boolean = complete(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, body) - def Error(body: String): Boolean = complete(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, body) - def NotImplemented(body: String): Boolean = complete(HttpServletResponse.SC_NOT_IMPLEMENTED, body) - def Unavailable(body: String, retry: Int): Boolean = complete(HttpServletResponse.SC_SERVICE_UNAVAILABLE, body, List(("Retry-After", retry.toString))) -} - - -case class Delete(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Get(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Head(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Options(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Post(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Put(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} -case class Trace(f:(()=>Option[tAsyncContext]), g:(()=>Option[AnyRef])) extends SuspendedRequest {init(f, g)} - diff --git a/akka-http/src/main/scala/Types.scala b/akka-http/src/main/scala/Types.scala index ba423194a3..d77920b82e 100644 --- a/akka-http/src/main/scala/Types.scala +++ b/akka-http/src/main/scala/Types.scala @@ -14,37 +14,18 @@ package akka.http */ object Types { - import javax.servlet. {ServletContext, ServletRequest, ServletResponse} + import javax.servlet. {ServletRequest, ServletResponse} type tAsyncRequest = { - def startAsync:tAsyncContext + def startAsync:tAsyncRequestContext } - type tAsyncContext = { + /** + * Used to match both AsyncContext and AsyncContinuation in order to complete the request + */ + type tAsyncRequestContext = { def complete:Unit - def dispatch:Unit - def dispatch(s:String):Unit - def dispatch(c:ServletContext, s:String) def getRequest:ServletRequest def getResponse:ServletResponse - def hasOriginalRequestAndResponse:Boolean - def setTimeout(ms:Long):Unit - def start(r:Runnable):Unit - } - - type tContinuation = { - def complete:Unit - def isExpired:Boolean - def isInitial:Boolean - def isResumed:Boolean - def isSuspended:Boolean - def resume:Unit - def suspend:Unit - def undispatch:Unit - } - - type tContinuationListener = { - def onComplete(c:tContinuation) - def onTimeout(c:tContinuation) } } \ No newline at end of file diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala index 38087b03db..1eeb9348a3 100644 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/Boot.scala @@ -4,7 +4,7 @@ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. */ -package sample.mist +package sample.rest.scala import akka.actor._ import akka.actor.Actor._ @@ -19,27 +19,16 @@ class Boot { val factory = SupervisorFactory( SupervisorConfig( OneForOneStrategy(List(classOf[Exception]), 3, 100), + // + // in this particular case, just boot the built-in default root endpoint + // Supervise( - actorOf[ServiceRoot], + actorOf[RootEndpoint], Permanent) :: Supervise( - actorOf[SimpleService], + actorOf[SimpleAkkaAsyncHttpService], Permanent) :: Nil)) factory.newInstance.start } - -class ServiceRoot extends RootEndpoint -{ - // - // use the configurable dispatcher - // - self.dispatcher = Endpoint.Dispatcher - - // - // TODO: make this a config prop - // - self.id = "DefaultRootEndpoint" -} - diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala similarity index 94% rename from akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala rename to akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala index 333cdb3c86..4fcb57de27 100644 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService2.scala +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala @@ -4,7 +4,7 @@ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. */ -package sample.mist +package sample.rest.scala import akka.actor._ import akka.actor.Actor._ @@ -18,7 +18,7 @@ import akka.http._ * * @author Garrick Evans */ -class SimpleService extends Actor with Endpoint +class SimpleAkkaAsyncHttpService extends Actor with Endpoint { final val ServiceRoot = "/simple/" final val ProvideSameActor = ServiceRoot + "same" @@ -147,9 +147,9 @@ class BoringActor extends Actor } } - case other if other.isInstanceOf[SuspendedRequest] => + case other if other.isInstanceOf[RequestMethod] => { - other.asInstanceOf[SuspendedRequest].NotAllowed("Invalid method for this endpoint") + other.asInstanceOf[RequestMethod].NotAllowed("Invalid method for this endpoint") } } } diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala index ccfd6fd286..896c405cb2 100644 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala @@ -22,7 +22,7 @@ import org.atmosphere.util.XSSHtmlFilter import org.atmosphere.cpr.{Broadcaster, BroadcastFilter} import org.atmosphere.jersey.Broadcastable -class Boot { +class BootPrev { val factory = SupervisorFactory( SupervisorConfig( OneForOneStrategy(List(classOf[Exception]), 3, 100), diff --git a/config/akka-reference.conf b/config/akka-reference.conf index 16aecf6bc9..f379cccb64 100644 --- a/config/akka-reference.conf +++ b/config/akka-reference.conf @@ -99,6 +99,13 @@ akka { } # maxInactiveActivity = 60000 # Atmosphere CometSupport maxInactiveActivity + connection-close = true # toggles the addition of the "Connection" response header with a "close" value + root-actor-id = "_httproot" # the id of the actor to use as the root endpoint + root-actor-builtin = true # toggles the use of the built-in root endpoint base class + timeout = 1000 # the default timeout for all async requests (in ms) + expired-header-name = "Async-Timeout" # the name of the response header to use when an async request expires + expired-header-value = "expired" # the value of the response header to use when an async request expires + # Uncomment if you are using the KerberosAuthenticationActor # kerberos { # servicePrincipal = "HTTP/localhost@EXAMPLE.COM" diff --git a/config/microkernel-server.xml b/config/microkernel-server.xml index 987dd22943..6a8ea4a67d 100644 --- a/config/microkernel-server.xml +++ b/config/microkernel-server.xml @@ -73,7 +73,7 @@ / - akka.comet.AkkaServlet + akka.http.AkkaHttpServlet /* From 11908ab605c6696c696ce3fbd4f697a8d1aa36fd Mon Sep 17 00:00:00 2001 From: Garrick Evans Date: Sat, 20 Nov 2010 22:48:06 -0800 Subject: [PATCH 04/17] hacking in servlet 3.0 support using embedded jetty-8 (remove atmo, hbase, volde to get around jar mismatch); wip --- .../src/main/scala/AkkaBroadcaster.scala | 40 ---- .../src/main/scala/AkkaCometServlet.scala | 101 --------- .../src/main/scala/AkkaHttpServlet.scala | 1 + .../src/main/scala/JettyContinuation.scala | 2 +- akka-http/src/main/scala/RequestMethod.scala | 2 +- .../src/main/scala/Servlet30Context.scala | 80 +++++++ .../scala/SimpleAkkaAsyncHttpService.scala | 2 + .../src/main/scala/SimpleService.scala | 198 ------------------ config/akka-reference.conf | 6 +- project/build/AkkaProject.scala | 131 +++++++----- 10 files changed, 171 insertions(+), 392 deletions(-) delete mode 100644 akka-http/src/main/scala/AkkaBroadcaster.scala delete mode 100644 akka-http/src/main/scala/AkkaCometServlet.scala create mode 100644 akka-http/src/main/scala/Servlet30Context.scala delete mode 100644 akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala diff --git a/akka-http/src/main/scala/AkkaBroadcaster.scala b/akka-http/src/main/scala/AkkaBroadcaster.scala deleted file mode 100644 index fd0f76631a..0000000000 --- a/akka-http/src/main/scala/AkkaBroadcaster.scala +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package akka.comet - -import org.atmosphere.cpr.{AtmosphereResourceEvent, AtmosphereResource} - -import akka.actor.Actor._ -import akka.actor.Actor -import akka.dispatch.Dispatchers -import org.atmosphere.jersey.util.JerseyBroadcasterUtil - -object AkkaBroadcaster { - val broadcasterDispatcher = Dispatchers.fromConfig("akka.rest.comet-dispatcher") - - type Event = AtmosphereResourceEvent[_,_] - type Resource = AtmosphereResource[_,_] -} - -class AkkaBroadcaster extends org.atmosphere.jersey.util.JerseySimpleBroadcaster { - import AkkaBroadcaster._ - - //FIXME should be supervised - lazy val caster = actorOf(new Actor { - self.dispatcher = broadcasterDispatcher - def receive = { - case (r: Resource,e: Event) => JerseyBroadcasterUtil.broadcast(r,e) - } - }).start - - override def destroy { - super.destroy - caster.stop - } - - protected override def broadcast(r: Resource, e : Event) { - caster ! ((r,e)) - } -} diff --git a/akka-http/src/main/scala/AkkaCometServlet.scala b/akka-http/src/main/scala/AkkaCometServlet.scala deleted file mode 100644 index 5b15096c92..0000000000 --- a/akka-http/src/main/scala/AkkaCometServlet.scala +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package akka.comet - -import akka.util.Logging - -import java.util.{List => JList} -import javax.servlet.{ServletConfig,ServletContext} -import javax.servlet.http.{HttpServletRequest, HttpServletResponse} -import com.sun.jersey.spi.container.servlet.ServletContainer - -import org.atmosphere.container.GrizzlyCometSupport -import org.atmosphere.cpr.{AtmosphereServlet, AtmosphereServletProcessor, AtmosphereResource, AtmosphereResourceEvent,CometSupport,CometSupportResolver,DefaultCometSupportResolver} -import org.atmosphere.handler.{ReflectorServletProcessor, AbstractReflectorAtmosphereHandler} - -class AtmosphereRestServlet extends ServletContainer with AtmosphereServletProcessor { - //Delegate to implement the behavior for AtmosphereHandler - private val handler = new AbstractReflectorAtmosphereHandler { - override def onRequest(event: AtmosphereResource[HttpServletRequest, HttpServletResponse]) { - if (event ne null) { - event.getRequest.setAttribute(AtmosphereServlet.ATMOSPHERE_RESOURCE, event) - event.getRequest.setAttribute(AtmosphereServlet.ATMOSPHERE_HANDLER, this) - service(event.getRequest, event.getResponse) - } - } - } - - override def onStateChange(event: AtmosphereResourceEvent[HttpServletRequest, HttpServletResponse]) { - if (event ne null) handler onStateChange event - } - - override def onRequest(resource: AtmosphereResource[HttpServletRequest, HttpServletResponse]) { - handler onRequest resource - } - } - -/** - * Akka's Comet servlet to be used when deploying actors exposed as Comet (and REST) services in a - * standard servlet container, e.g. not using the Akka Kernel. - *

- * Used by the Akka Kernel to bootstrap REST and Comet. - */ -class AkkaServlet extends AtmosphereServlet { - import akka.config.Config.{config => c} - - /* - * Configure Atmosphere and Jersey (default, fall-back values) - */ - addInitParameter(AtmosphereServlet.DISABLE_ONSTATE_EVENT,"true") - addInitParameter(AtmosphereServlet.BROADCASTER_CLASS,classOf[AkkaBroadcaster].getName) - addInitParameter(AtmosphereServlet.PROPERTY_USE_STREAM,"true") - addInitParameter("com.sun.jersey.config.property.packages",c.getList("akka.rest.resource_packages").mkString(";")) - addInitParameter("com.sun.jersey.spi.container.ResourceFilters",c.getList("akka.rest.filters").mkString(",")) - - c.getInt("akka.rest.maxInactiveActivity") foreach { value => addInitParameter(CometSupport.MAX_INACTIVE,value.toString) } - c.getString("akka.rest.cometSupport") foreach { value => addInitParameter("cometSupport",value) } - - /* - * Provide a fallback for default values - */ - override def getInitParameter(key : String) = - Option(super.getInitParameter(key)).getOrElse(initParams get key) - - /* - * Provide a fallback for default values - */ - override def getInitParameterNames() = { - import scala.collection.JavaConversions._ - initParams.keySet.iterator ++ super.getInitParameterNames - } - - /** - * We override this to avoid Atmosphere looking for it's atmosphere.xml file - * Instead we specify what semantics we want in code. - */ - override def loadConfiguration(sc: ServletConfig) { - config.setSupportSession(false) - isBroadcasterSpecified = true - - //The bridge between Atmosphere and Jersey - val servlet = new AtmosphereRestServlet { - //These are needed to make sure that Jersey is reading the config from the outer servlet - override def getInitParameter(key : String) = AkkaServlet.this.getInitParameter(key) - override def getInitParameterNames() = AkkaServlet.this.getInitParameterNames() - } - - addAtmosphereHandler("/*", servlet, new AkkaBroadcaster) - } - - override lazy val createCometSupportResolver: CometSupportResolver = new DefaultCometSupportResolver(config) { - import scala.collection.JavaConversions._ - - lazy val desiredCometSupport = - Option(AkkaServlet.this.getInitParameter("cometSupport")) filter testClassExists map newCometSupport - - override def resolve(useNativeIfPossible : Boolean, useBlockingAsDefault : Boolean) : CometSupport[_ <: AtmosphereResource[_,_]] = - desiredCometSupport.getOrElse(super.resolve(useNativeIfPossible, useBlockingAsDefault)) - } -} diff --git a/akka-http/src/main/scala/AkkaHttpServlet.scala b/akka-http/src/main/scala/AkkaHttpServlet.scala index 61f2070ab0..12f48addfc 100644 --- a/akka-http/src/main/scala/AkkaHttpServlet.scala +++ b/akka-http/src/main/scala/AkkaHttpServlet.scala @@ -88,6 +88,7 @@ class AkkaHttpServlet extends HttpServlet with Logging case (3,0) => { log.info("Supporting Java asynchronous contexts.") + _factory = Some(Servlet30ContextMethodFactory) } case _ if (server.toLowerCase startsWith JettyServer) => { diff --git a/akka-http/src/main/scala/JettyContinuation.scala b/akka-http/src/main/scala/JettyContinuation.scala index 6c753382f2..ff0355a82a 100644 --- a/akka-http/src/main/scala/JettyContinuation.scala +++ b/akka-http/src/main/scala/JettyContinuation.scala @@ -14,7 +14,7 @@ import Types._ /** * @author Garrick Evans */ -trait JettyContinuation extends ContinuationListener with akka.util.Logging +trait JettyContinuation extends ContinuationListener with akka.util.Logging { import javax.servlet.http.HttpServletResponse import AkkaHttpServlet._ diff --git a/akka-http/src/main/scala/RequestMethod.scala b/akka-http/src/main/scala/RequestMethod.scala index ba86336521..e475d86714 100644 --- a/akka-http/src/main/scala/RequestMethod.scala +++ b/akka-http/src/main/scala/RequestMethod.scala @@ -101,7 +101,7 @@ trait RequestMethod extends Logging } catch { - case io:IOException => log.error(io, "Failed to write data to connection on resume - the client probably disconnected") + case io => log.error(io, "Failed to write data to connection on resume - the client probably disconnected") } } diff --git a/akka-http/src/main/scala/Servlet30Context.scala b/akka-http/src/main/scala/Servlet30Context.scala new file mode 100644 index 0000000000..5d48118609 --- /dev/null +++ b/akka-http/src/main/scala/Servlet30Context.scala @@ -0,0 +1,80 @@ +/** + * Copyright 2010 Autodesk, Inc. All rights reserved. + * Licensed under Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + */ + +package akka.http + +import javax.servlet. {AsyncContext, AsyncListener, AsyncEvent}; +import Types._ + + +/** + * @author Garrick Evans + */ +trait Servlet30Context extends AsyncListener with akka.util.Logging +{ + import javax.servlet.http.HttpServletResponse + import AkkaHttpServlet._ + + val builder:()=>tAsyncRequestContext + val context:Option[tAsyncRequestContext] = Some(builder()) + def go = {context.isDefined} + + protected val _ac:AsyncContext = { + val ac = context.get.asInstanceOf[AsyncContext] + ac.setTimeout(DefaultTimeout) + ac.addListener(this) + ac + } + + def suspended:Boolean = true + + def timeout(ms:Long):Boolean = + { + try { + _ac.setTimeout(ms) + true + } + catch { + case ex:IllegalStateException => { + + log.info("Cannot update timeout - already returned to container") + false + } + } + } + + // + // AsyncListener + // + + def onComplete(e:AsyncEvent) = {} + def onError(e:AsyncEvent) = + { + e.getThrowable match { + case null => log.warning("Error occured...") + case t => log.warning(t, "Error occured") + } + } + def onStartAsync(e:AsyncEvent) = {} + def onTimeout(e:AsyncEvent) = + { + e.getSuppliedResponse.asInstanceOf[HttpServletResponse].addHeader(ExpiredHeaderName, ExpiredHeaderValue) + e.getAsyncContext.complete + } +} + +object Servlet30ContextMethodFactory extends RequestMethodFactory +{ + def Delete(f:(()=>tAsyncRequestContext)):RequestMethod = {new Delete(f) with Servlet30Context} + def Get(f:(()=>tAsyncRequestContext)):RequestMethod = {new Get(f) with Servlet30Context} + def Head(f:(()=>tAsyncRequestContext)):RequestMethod = {new Head(f) with Servlet30Context} + def Options(f:(()=>tAsyncRequestContext)):RequestMethod = {new Options(f) with Servlet30Context} + def Post(f:(()=>tAsyncRequestContext)):RequestMethod = {new Post(f) with Servlet30Context} + def Put(f:(()=>tAsyncRequestContext)):RequestMethod = {new Put(f) with Servlet30Context} + def Trace(f:(()=>tAsyncRequestContext)):RequestMethod = {new Trace(f) with Servlet30Context} +} + + diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala index 4fcb57de27..4a6802558b 100644 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala +++ b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleAkkaAsyncHttpService.scala @@ -99,6 +99,8 @@ class BoringActor extends Actor // get.response.setContentType(MediaType.TEXT_HTML) + get.timeout(5000) + // // "work" // diff --git a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala b/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala deleted file mode 100644 index 896c405cb2..0000000000 --- a/akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package sample.rest.scala - -import akka.actor.{Transactor, SupervisorFactory, Actor} -import akka.actor.Actor._ -import akka.stm.TransactionalMap -import akka.persistence.cassandra.CassandraStorage -import akka.config.Supervision._ -import akka.util.Logging -import akka.comet.AkkaClusterBroadcastFilter -import scala.xml.NodeSeq -import java.lang.Integer -import java.nio.ByteBuffer -import javax.ws.rs.core.MultivaluedMap -import javax.ws.rs.{GET, POST, Path, Produces, WebApplicationException, Consumes,PathParam} -import akka.actor.ActorRegistry.actorFor -import org.atmosphere.annotation.{Broadcast, Suspend,Cluster} -import org.atmosphere.util.XSSHtmlFilter -import org.atmosphere.cpr.{Broadcaster, BroadcastFilter} -import org.atmosphere.jersey.Broadcastable - -class BootPrev { - val factory = SupervisorFactory( - SupervisorConfig( - OneForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - actorOf[SimpleServiceActor], - Permanent) :: - Supervise( - actorOf[ChatActor], - Permanent) :: - Supervise( - actorOf[PersistentSimpleServiceActor], - Permanent) - :: Nil)) - factory.newInstance.start -} - -/** - * Try service out by invoking (multiple times): - *

- * curl http://localhost:9998/scalacount
- * 
- * Or browse to the URL from a web browser. - */ -@Path("/scalacount") -class SimpleService { - @GET - @Produces(Array("text/html")) - def count = { - //Fetch the first actor of type SimpleServiceActor - //Send it the "Tick" message and expect a NodeSeq back - val result = for{a <- actorFor[SimpleServiceActor] - r <- (a !! "Tick").as[NodeSeq]} yield r - //Return either the resulting NodeSeq or a default one - result getOrElse Error in counter - } -} - -class SimpleServiceActor extends Transactor { - private val KEY = "COUNTER" - private var hasStartedTicking = false - private lazy val storage = TransactionalMap[String, Integer]() - - def receive = { - case "Tick" => if (hasStartedTicking) { - val counter = storage.get(KEY).get.asInstanceOf[Integer].intValue - storage.put(KEY, new Integer(counter + 1)) - self.reply(Tick:{counter + 1}) - } else { - storage.put(KEY, new Integer(0)) - hasStartedTicking = true - self.reply(Tick: 0) - } - } -} - -@Path("/pubsub/") -class PubSub { - @GET - @Suspend - @Produces(Array("text/plain;charset=ISO-8859-1")) - @Path("/topic/{topic}/") - def subscribe(@PathParam("topic") topic: Broadcaster): Broadcastable = new Broadcastable("", topic) - - @GET - @Broadcast - @Path("/topic/{topic}/{message}/") - @Produces(Array("text/plain;charset=ISO-8859-1")) - //FIXME @Cluster(value = Array(classOf[AkkaClusterBroadcastFilter]),name = "foo") - def say(@PathParam("topic") topic: Broadcaster, @PathParam("message") message: String): Broadcastable = new Broadcastable(message, topic) -} - -/** - * Try service out by invoking (multiple times): - *
- * curl http://localhost:9998/persistentscalacount
- * 
- * Or browse to the URL from a web browser. - */ -@Path("/persistentscalacount") -class PersistentSimpleService { - @GET - @Produces(Array("text/html")) - def count = { - //Fetch the first actor of type PersistentSimpleServiceActor - //Send it the "Tick" message and expect a NodeSeq back - val result = for{a <- actorFor[PersistentSimpleServiceActor] - r <- (a !! "Tick").as[NodeSeq]} yield r - //Return either the resulting NodeSeq or a default one - result getOrElse Error in counter - } -} - -class PersistentSimpleServiceActor extends Transactor { - private val KEY = "COUNTER" - private var hasStartedTicking = false - private lazy val storage = CassandraStorage.newMap - - def receive = { - case "Tick" => if (hasStartedTicking) { - val bytes = storage.get(KEY.getBytes).get - val counter = Integer.parseInt(new String(bytes, "UTF8")) - storage.put(KEY.getBytes, (counter + 1).toString.getBytes ) -// val bytes = storage.get(KEY.getBytes).get -// val counter = ByteBuffer.wrap(bytes).getInt -// storage.put(KEY.getBytes, ByteBuffer.allocate(4).putInt(counter + 1).array) - self.reply(Tick:{counter + 1}) - } else { - storage.put(KEY.getBytes, "0".getBytes) -// storage.put(KEY.getBytes, Array(0.toByte)) - hasStartedTicking = true - self.reply(Tick: 0) - } - } -} - -@Path("/chat") -class Chat { - import ChatActor.ChatMsg - @Suspend - @GET - @Produces(Array("text/html")) - def suspend = () - - @POST - @Broadcast(Array(classOf[XSSHtmlFilter], classOf[JsonpFilter])) - //FIXME @Cluster(value = Array(classOf[AkkaClusterBroadcastFilter]),name = "bar") - @Consumes(Array("application/x-www-form-urlencoded")) - @Produces(Array("text/html")) - def publishMessage(form: MultivaluedMap[String, String]) = { - val msg = ChatMsg(form.getFirst("name"),form.getFirst("action"),form.getFirst("message")) - //Fetch the first actor of type ChatActor - //Send it the "Tick" message and expect a NodeSeq back - val result = for{a <- actorFor[ChatActor] - r <- (a !! msg).as[String]} yield r - //Return either the resulting String or a default one - result getOrElse "System__error" - } -} - -object ChatActor { - case class ChatMsg(val who: String, val what: String, val msg: String) -} - -class ChatActor extends Actor with Logging { - import ChatActor.ChatMsg - def receive = { - case ChatMsg(who, what, msg) => { - what match { - case "login" => self.reply("System Message__" + who + " has joined.") - case "post" => self.reply("" + who + "__" + msg) - case _ => throw new WebApplicationException(422) - } - } - case x => log.info("recieve unknown: " + x) - } -} - - -class JsonpFilter extends BroadcastFilter with Logging { - def filter(an: AnyRef) = { - val m = an.toString - var name = m - var message = "" - - if (m.indexOf("__") > 0) { - name = m.substring(0, m.indexOf("__")) - message = m.substring(m.indexOf("__") + 2) - } - - new BroadcastFilter.BroadcastAction("\n") - } -} diff --git a/config/akka-reference.conf b/config/akka-reference.conf index f379cccb64..c1b844e779 100644 --- a/config/akka-reference.conf +++ b/config/akka-reference.conf @@ -13,8 +13,8 @@ akka { # These boot classes are loaded (and created) automatically when the Akka Microkernel boots up # Can be used to bootstrap your application(s) # Should be the FQN (Fully Qualified Name) of the boot class which needs to have a default constructor - boot = ["sample.camel.Boot", - "sample.rest.java.Boot", + boot = [#"sample.camel.Boot", + #"sample.rest.java.Boot", "sample.rest.scala.Boot", "sample.security.Boot"] @@ -222,6 +222,6 @@ akka { } camel { - service = on + service = off } } diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 57510103d3..abfe3c2fc1 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -1,6 +1,6 @@ - /*---------------------------------------------------------------------------\ -| Copyright (C) 2009-2010 Scalable Solutions AB | -\---------------------------------------------------------------------------*/ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ import com.weiglewilczek.bnd4sbt.BNDPlugin import java.io.File @@ -102,7 +102,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val jmxModuleConfig = ModuleConfiguration("com.sun.jmx", SunJDMKRepo) lazy val jerseyContrModuleConfig = ModuleConfiguration("com.sun.jersey.contribs", JavaNetRepo) lazy val jerseyModuleConfig = ModuleConfiguration("com.sun.jersey", JavaNetRepo) - lazy val jgroupsModuleConfig = ModuleConfiguration("jgroups", JBossRepo) lazy val multiverseModuleConfig = ModuleConfiguration("org.multiverse", CodehausRepo) lazy val nettyModuleConfig = ModuleConfiguration("org.jboss.netty", JBossRepo) lazy val scalaTestModuleConfig = ModuleConfiguration("org.scalatest", ScalaToolsRelRepo) @@ -133,8 +132,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val SLF4J_VERSION = "1.6.0" lazy val SPRING_VERSION = "3.0.4.RELEASE" lazy val ASPECTWERKZ_VERSION = "2.2.2" - lazy val JETTY_VERSION = "7.1.6.v20100715" - lazy val JAVAX_SERVLET_VERSION = "3.0" + lazy val JETTY_VERSION = "8.0.0.M1" + //lazy val JAVAX_SERVLET_VERSION = "3.0" // ------------------------------------------------------------------------------------------------------------------- // Dependencies @@ -173,7 +172,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val dispatch_http = "net.databinder" % "dispatch-http_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 lazy val dispatch_json = "net.databinder" % "dispatch-json_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 - lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "compile" //CDDL v1 + //lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "compile" //CDDL v1 lazy val jetty = "org.eclipse.jetty" % "jetty-server" % JETTY_VERSION % "compile" //Eclipse license lazy val jetty_util = "org.eclipse.jetty" % "jetty-util" % JETTY_VERSION % "compile" //Eclipse license @@ -196,8 +195,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val jersey_server = "com.sun.jersey" % "jersey-server" % JERSEY_VERSION % "compile" //CDDL v1 lazy val jersey_contrib = "com.sun.jersey.contribs" % "jersey-scala" % JERSEY_VERSION % "compile" //CDDL v1 - lazy val jgroups = "jgroups" % "jgroups" % "2.9.0.GA" % "compile" //LGPL 2.1 - lazy val jsr166x = "jsr166x" % "jsr166x" % "1.0" % "compile" //CC Public Domain lazy val jsr250 = "javax.annotation" % "jsr250-api" % "1.0" % "compile" //CDDL v1 @@ -211,6 +208,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val casbah = "com.novus" % "casbah_2.8.0" % "1.0.8.5" % "compile" //ApacheV2 lazy val multiverse = "org.multiverse" % "multiverse-alpha" % MULTIVERSE_VERSION % "compile" intransitive //ApacheV2 + lazy val multiverse_test = "org.multiverse" % "multiverse-alpha" % MULTIVERSE_VERSION % "test" intransitive //ApacheV2 lazy val netty = "org.jboss.netty" % "netty" % "3.2.3.Final" % "compile" //ApacheV2 @@ -239,9 +237,9 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val thrift = "com.facebook" % "thrift" % "r917130" % "compile" //ApacheV2 - lazy val voldemort = "voldemort" % "voldemort" % "0.81" % "compile" //ApacheV2 - lazy val voldemort_contrib = "voldemort" % "voldemort-contrib" % "0.81" % "compile" //ApacheV2 - lazy val voldemort_needs_log4j = "org.slf4j" % "log4j-over-slf4j" % SLF4J_VERSION % "compile" //MIT +// lazy val voldemort = "voldemort" % "voldemort" % "0.81" % "compile" //ApacheV2 +// lazy val voldemort_contrib = "voldemort" % "voldemort-contrib" % "0.81" % "compile" //ApacheV2 +// lazy val voldemort_needs_log4j = "org.slf4j" % "log4j-over-slf4j" % SLF4J_VERSION % "compile" //MIT lazy val werkz = "org.codehaus.aspectwerkz" % "aspectwerkz-nodeps-jdk5" % ASPECTWERKZ_VERSION % "compile" //LGPL 2.1 lazy val werkz_core = "org.codehaus.aspectwerkz" % "aspectwerkz-jdk5" % ASPECTWERKZ_VERSION % "compile" //LGPL 2.1 @@ -250,12 +248,13 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val hadoop_core = "org.apache.hadoop" % "hadoop-core" % "0.20.2" % "compile" //ApacheV2 - lazy val hbase_core = "org.apache.hbase" % "hbase-core" % "0.20.6" % "compile" //ApacheV2 + //lazy val hbase_core = "org.apache.hbase" % "hbase-core" % "0.20.6" % "compile" //ApacheV2 lazy val google_coll = "com.google.collections" % "google-collections" % "1.0" % "compile" //ApacheV2 //Riak PB Client lazy val riak_pb_client = "com.trifork" % "riak-java-pb-client" % "1.0-for-akka-by-ticktock" % "compile" //ApacheV2 + lazy val scalaj_coll = "org.scalaj" % "scalaj-collection_2.8.0" % "1.0" % "compile" //ApacheV2 // Test @@ -276,16 +275,19 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val hadoop_test = "org.apache.hadoop" % "hadoop-test" % "0.20.2" % "test" //ApacheV2 lazy val hbase_test = "org.apache.hbase" % "hbase-test" % "0.20.6" % "test" //ApacheV2 lazy val log4j = "log4j" % "log4j" % "1.2.15" % "test" //ApacheV2 - lazy val jetty_mortbay = "org.mortbay.jetty" % "jetty" % "6.1.14" % "test" //Eclipse license + //lazy val jetty_mortbay = "org.mortbay.jetty" % "jetty" % "6.1.14" % "test" //Eclipse license //voldemort testing lazy val jdom = "org.jdom" % "jdom" % "1.1" % "test" //JDOM license: ApacheV2 - acknowledgement - lazy val vold_jetty = "org.mortbay.jetty" % "jetty" % "6.1.18" % "test" //ApacheV2 + //lazy val vold_jetty = "org.mortbay.jetty" % "jetty" % "6.1.18" % "test" //ApacheV2 lazy val velocity = "org.apache.velocity" % "velocity" % "1.6.2" % "test" //ApacheV2 lazy val dbcp = "commons-dbcp" % "commons-dbcp" % "1.2.2" % "test" //ApacheV2 //memcached - lazy val spymemcached = "spy" % "memcached" % "2.5" % "compile" //MIT + lazy val spymemcached = "spy" % "memcached" % "2.5" % "compile" + + //simpledb + lazy val simpledb = "com.amazonaws" % "aws-java-sdk" % "1.0.14" % "compile" } // ------------------------------------------------------------------------------------------------------------------- @@ -293,14 +295,15 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // ------------------------------------------------------------------------------------------------------------------- lazy val akka_actor = project("akka-actor", "akka-actor", new AkkaActorProject(_)) - lazy val akka_typed_actor = project("akka-typed-actor", "akka-typed-actor", new AkkaTypedActorProject(_), akka_actor) + lazy val akka_stm = project("akka-stm", "akka-stm", new AkkaStmProject(_), akka_actor) + lazy val akka_typed_actor = project("akka-typed-actor", "akka-typed-actor", new AkkaTypedActorProject(_), akka_stm) lazy val akka_remote = project("akka-remote", "akka-remote", new AkkaRemoteProject(_), akka_typed_actor) lazy val akka_amqp = project("akka-amqp", "akka-amqp", new AkkaAMQPProject(_), akka_remote) lazy val akka_http = project("akka-http", "akka-http", new AkkaHttpProject(_), akka_remote, akka_camel) lazy val akka_camel = project("akka-camel", "akka-camel", new AkkaCamelProject(_), akka_remote) lazy val akka_persistence = project("akka-persistence", "akka-persistence", new AkkaPersistenceParentProject(_)) lazy val akka_spring = project("akka-spring", "akka-spring", new AkkaSpringProject(_), akka_remote, akka_camel) - lazy val akka_jta = project("akka-jta", "akka-jta", new AkkaJTAProject(_), akka_remote) + lazy val akka_jta = project("akka-jta", "akka-jta", new AkkaJTAProject(_), akka_stm, akka_remote) lazy val akka_kernel = project("akka-kernel", "akka-kernel", new AkkaKernelProject(_), akka_remote, akka_jta, akka_http, akka_spring, akka_camel, akka_persistence, akka_amqp) lazy val akka_osgi = project("akka-osgi", "akka-osgi", new AkkaOSGiParentProject(_)) @@ -316,8 +319,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { manifestClassPath.map(cp => ManifestAttributes( (Attributes.Name.CLASS_PATH, cp), (IMPLEMENTATION_TITLE, "Akka"), - (IMPLEMENTATION_URL, "http://akkasource.org"), - (IMPLEMENTATION_VENDOR, "The Akka Project") + (IMPLEMENTATION_URL, "http://akka.io"), + (IMPLEMENTATION_VENDOR, "Scalable Solutions AB") )).toList ::: getMainClass(false).map(MainClass(_)).toList @@ -331,6 +334,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { " config/" + " scala-library.jar" + " dist/akka-actor_%s-%s.jar".format(buildScalaVersion, version) + + " dist/akka-stm_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-typed-actor_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-remote_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-http_%s-%s.jar".format(buildScalaVersion, version) + @@ -340,9 +344,9 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { " dist/akka-persistence-redis_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-persistence-mongo_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-persistence-cassandra_%s-%s.jar".format(buildScalaVersion, version) + - " dist/akka-persistence-voldemort_%s-%s.jar".format(buildScalaVersion, version) + + //" dist/akka-persistence-voldemort_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-persistence-riak_%s-%s.jar".format(buildScalaVersion, version) + - " dist/akka-persistence-hbase_%s-%s.jar".format(buildScalaVersion, version) + + //" dist/akka-persistence-hbase_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-kernel_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-spring_%s-%s.jar".format(buildScalaVersion, version) + " dist/akka-jta_%s-%s.jar".format(buildScalaVersion, version) @@ -399,7 +403,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val artifactRE(path, artifactId, artifactVersion) = absPath val command = "mvn install:install-file" + " -Dfile=" + absPath + - " -DgroupId=akka" + + " -DgroupId=se.scalablesolutions.akka" + " -DartifactId=" + artifactId + " -Dversion=" + version + " -Dpackaging=jar -DgeneratePom=true" @@ -416,15 +420,27 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val uuid = Dependencies.uuid val configgy = Dependencies.configgy val hawtdispatch = Dependencies.hawtdispatch - val multiverse = Dependencies.multiverse val jsr166x = Dependencies.jsr166x val slf4j = Dependencies.slf4j val logback = Dependencies.logback val logback_core = Dependencies.logback_core // testing - val junit = Dependencies.junit - val scalatest = Dependencies.scalatest + val junit = Dependencies.junit + val scalatest = Dependencies.scalatest + val multiverse_test = Dependencies.multiverse_test // StandardLatch + } + + // ------------------------------------------------------------------------------------------------------------------- + // akka-stm subproject + // ------------------------------------------------------------------------------------------------------------------- + + class AkkaStmProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { + val multiverse = Dependencies.multiverse + + // testing + val junit = Dependencies.junit + val scalatest = Dependencies.scalatest } // ------------------------------------------------------------------------------------------------------------------- @@ -455,7 +471,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val h2_lzf = Dependencies.h2_lzf val jackson = Dependencies.jackson val jackson_core = Dependencies.jackson_core - val jgroups = Dependencies.jgroups val jta_1_1 = Dependencies.jta_1_1 val netty = Dependencies.netty val protobuf = Dependencies.protobuf @@ -492,12 +507,12 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaHttpProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { val jsr250 = Dependencies.jsr250 - val atmo = Dependencies.atmo - val atmo_jbossweb = Dependencies.atmo_jbossweb - val atmo_jersey = Dependencies.atmo_jersey - val atmo_runtime = Dependencies.atmo_runtime - val atmo_tomcat = Dependencies.atmo_tomcat - val atmo_weblogic = Dependencies.atmo_weblogic + //val atmo = Dependencies.atmo + //val atmo_jbossweb = Dependencies.atmo_jbossweb + //val atmo_jersey = Dependencies.atmo_jersey + //val atmo_runtime = Dependencies.atmo_runtime + //val atmo_tomcat = Dependencies.atmo_tomcat + //val atmo_weblogic = Dependencies.atmo_weblogic val jetty = Dependencies.jetty val jetty_util = Dependencies.jetty_util val jetty_xml = Dependencies.jetty_xml @@ -509,7 +524,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val jersey_server = Dependencies.jersey_server val jsr311 = Dependencies.jsr311 val stax_api = Dependencies.stax_api - val servlet30 = Dependencies.javax_servlet_30 + //val servlet30 = Dependencies.javax_servlet_30 // testing val junit = Dependencies.junit @@ -533,23 +548,25 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaPersistenceParentProject(info: ProjectInfo) extends ParentProject(info) { lazy val akka_persistence_common = project("akka-persistence-common", "akka-persistence-common", - new AkkaPersistenceCommonProject(_), akka_remote) + new AkkaPersistenceCommonProject(_), akka_remote, akka_stm) lazy val akka_persistence_redis = project("akka-persistence-redis", "akka-persistence-redis", new AkkaRedisProject(_), akka_persistence_common) lazy val akka_persistence_mongo = project("akka-persistence-mongo", "akka-persistence-mongo", new AkkaMongoProject(_), akka_persistence_common) lazy val akka_persistence_cassandra = project("akka-persistence-cassandra", "akka-persistence-cassandra", new AkkaCassandraProject(_), akka_persistence_common) - lazy val akka_persistence_hbase = project("akka-persistence-hbase", "akka-persistence-hbase", - new AkkaHbaseProject(_), akka_persistence_common) - lazy val akka_persistence_voldemort = project("akka-persistence-voldemort", "akka-persistence-voldemort", - new AkkaVoldemortProject(_), akka_persistence_common) + //lazy val akka_persistence_hbase = project("akka-persistence-hbase", "akka-persistence-hbase", + // new AkkaHbaseProject(_), akka_persistence_common) + //lazy val akka_persistence_voldemort = project("akka-persistence-voldemort", "akka-persistence-voldemort", + // new AkkaVoldemortProject(_), akka_persistence_common) lazy val akka_persistence_riak = project("akka-persistence-riak", "akka-persistence-riak", new AkkaRiakProject(_), akka_persistence_common) lazy val akka_persistence_couchdb = project("akka-persistence-couchdb", "akka-persistence-couchdb", new AkkaCouchDBProject(_), akka_persistence_common) lazy val akka_persistence_memcached= project("akka-persistence-memcached", "akka-persistence-memcached", new AkkaMemcachedProject(_), akka_persistence_common) + lazy val akka_persistence_simpledb= project("akka-persistence-simpledb", "akka-persistence-simpledb", + new AkkaSimpledbProject(_), akka_persistence_common) } // ------------------------------------------------------------------------------------------------------------------- @@ -559,6 +576,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaPersistenceCommonProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { val commons_pool = Dependencies.commons_pool val thrift = Dependencies.thrift + val scalaj_coll = Dependencies.scalaj_coll } // ------------------------------------------------------------------------------------------------------------------- @@ -603,7 +621,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // ------------------------------------------------------------------------------------------------------------------- // akka-persistence-hbase subproject // ------------------------------------------------------------------------------------------------------------------- - + /* class AkkaHbaseProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { override def ivyXML = @@ -630,6 +648,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { override def testOptions = createTestFilter( _.endsWith("Test") ) } + // ------------------------------------------------------------------------------------------------------------------- // akka-persistence-voldemort subproject // ------------------------------------------------------------------------------------------------------------------- @@ -649,8 +668,9 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { override def testOptions = createTestFilter({ s:String=> s.endsWith("Suite") || s.endsWith("Test")}) } - -// akka-persistence-riak subproject + */ + // ------------------------------------------------------------------------------------------------------------------- + // akka-persistence-riak subproject // ------------------------------------------------------------------------------------------------------------------- class AkkaRiakProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { @@ -663,6 +683,10 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { override def testOptions = createTestFilter(_.endsWith("Test")) } + // ------------------------------------------------------------------------------------------------------------------- + // akka-persistence-couchdb subproject + // ------------------------------------------------------------------------------------------------------------------- + class AkkaCouchDBProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { val couch = Dependencies.commonsHttpClient val spec = Dependencies.specs @@ -679,6 +703,16 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { override def testOptions = createTestFilter( _.endsWith("Test")) } + class AkkaSimpledbProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { + val memcached = Dependencies.simpledb + val commons_codec = Dependencies.commons_codec + val http = Dependencies.commonsHttpClient + + val scalatest = Dependencies.scalatest + + override def testOptions = createTestFilter( _.endsWith("Test")) + } + // ------------------------------------------------------------------------------------------------------------------- // akka-kernel subproject // ------------------------------------------------------------------------------------------------------------------- @@ -726,8 +760,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { new AkkaOSGiAssemblyProject(_), akka_osgi_dependencies_bundle, akka_remote, akka_amqp, akka_http, akka_camel, akka_spring, akka_jta, akka_persistence.akka_persistence_common, akka_persistence.akka_persistence_redis, akka_persistence.akka_persistence_mongo, - akka_persistence.akka_persistence_cassandra,akka_persistence.akka_persistence_hbase, - akka_persistence.akka_persistence_voldemort) + akka_persistence.akka_persistence_cassandra)//,akka_persistence.akka_persistence_hbase, + //akka_persistence.akka_persistence_voldemort) } class AkkaOSGiDependenciesBundleProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) with BNDPlugin { @@ -831,6 +865,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val jsr311 = Dependencies.jsr311 } + /** class AkkaSampleCamelProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) { //Must be like this to be able to exclude the geronimo-servlet_2.4_spec which is a too old Servlet spec override def ivyXML = @@ -849,7 +884,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { override def testOptions = createTestFilter( _.endsWith("Test")) - } + } **/ class AkkaSampleSecurityProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) { val commons_codec = Dependencies.commons_codec @@ -865,7 +900,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaSamplesParentProject(info: ProjectInfo) extends ParentProject(info) { lazy val akka_sample_ants = project("akka-sample-ants", "akka-sample-ants", - new AkkaSampleAntsProject(_), akka_remote) + new AkkaSampleAntsProject(_), akka_stm) lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat", new AkkaSampleChatProject(_), akka_kernel) lazy val akka_sample_pubsub = project("akka-sample-pubsub", "akka-sample-pubsub", @@ -876,8 +911,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { new AkkaSampleRestJavaProject(_), akka_kernel) lazy val akka_sample_rest_scala = project("akka-sample-rest-scala", "akka-sample-rest-scala", new AkkaSampleRestScalaProject(_), akka_kernel) - lazy val akka_sample_camel = project("akka-sample-camel", "akka-sample-camel", - new AkkaSampleCamelProject(_), akka_kernel) + //lazy val akka_sample_camel = project("akka-sample-camel", "akka-sample-camel", + // new AkkaSampleCamelProject(_), akka_kernel) lazy val akka_sample_security = project("akka-sample-security", "akka-sample-security", new AkkaSampleSecurityProject(_), akka_kernel) lazy val akka_sample_remote = project("akka-sample-remote", "akka-sample-remote", From e5a9087dbaf0025b6958c870dd89eafa8dc8e333 Mon Sep 17 00:00:00 2001 From: Garrick Evans Date: Sat, 20 Nov 2010 23:09:50 -0800 Subject: [PATCH 05/17] merge master to branch --- .../src/main/scala/akka/actor/Actor.scala | 505 ++++++ .../src/main/scala/akka/actor/ActorRef.scala | 1536 +++++++++++++++++ .../main/scala/akka/actor/ActorRegistry.scala | 438 +++++ .../actor/BootableActorLoaderService.scala | 101 ++ .../src/main/scala/akka/actor/FSM.scala | 146 ++ .../src/main/scala/akka/actor/Implicits.scala | 22 + .../src/main/scala/akka/actor/Scheduler.scala | 133 ++ .../main/scala/akka/actor/Supervisor.scala | 180 ++ .../main/scala/akka/actor/UntypedActor.scala | 161 ++ .../src/main/scala/akka/config/Config.scala | 123 ++ .../scala/akka/config/Configuration.scala | 60 + .../main/scala/akka/config/Configurator.scala | 21 + .../scala/akka/config/SupervisionConfig.scala | 136 ++ .../akka/dataflow/DataFlowVariable.scala | 195 +++ .../scala/akka/dispatch/Dispatchers.scala | 222 +++ .../ExecutorBasedEventDrivenDispatcher.scala | 224 +++ ...sedEventDrivenWorkStealingDispatcher.scala | 256 +++ .../src/main/scala/akka/dispatch/Future.scala | 264 +++ .../scala/akka/dispatch/HawtDispatcher.scala | 212 +++ .../scala/akka/dispatch/MailboxHandling.scala | 108 ++ .../scala/akka/dispatch/MessageHandling.scala | 179 ++ .../akka/dispatch/ThreadBasedDispatcher.scala | 52 + .../akka/dispatch/ThreadPoolBuilder.scala | 264 +++ .../src/main/scala/akka/japi/JavaAPI.scala | 86 + .../main/scala/akka/routing/Iterators.scala | 46 + .../main/scala/akka/routing/Listeners.scala | 37 + .../src/main/scala/akka/routing/Routers.scala | 73 + .../src/main/scala/akka/routing/Routing.scala | 61 + .../src/main/scala/akka/util/Address.scala | 23 + .../main/scala/akka/util/AkkaException.scala | 52 + .../src/main/scala/akka/util/Bootable.scala | 10 + .../src/main/scala/akka/util/Crypt.scala | 45 + .../src/main/scala/akka/util/Duration.scala | 108 ++ .../src/main/scala/akka/util/HashCode.scala | 57 + .../src/main/scala/akka/util/Helpers.scala | 100 ++ .../scala/akka/util/ListenerManagement.scala | 76 + .../src/main/scala/akka/util/LockUtil.scala | 167 ++ .../src/main/scala/akka/util/Logging.scala | 170 ++ .../scala/akka/util/ReflectiveAccess.scala | 272 +++ akka-actor/src/test/scala/akka/Messages.scala | 13 + .../ActorFireForgetRequestReplySpec.scala | 91 + .../scala/akka/actor/actor/ActorRefSpec.scala | 101 ++ .../test/scala/akka/actor/actor/Bench.scala | 119 ++ .../scala/akka/actor/actor/FSMActorSpec.scala | 126 ++ .../akka/actor/actor/ForwardActorSpec.scala | 81 + .../scala/akka/actor/actor/HotSwapSpec.scala | 145 ++ .../akka/actor/actor/ReceiveTimeoutSpec.scala | 104 ++ .../supervisor/RestartStrategySpec.scala | 275 +++ .../supervisor/SupervisorHierarchySpec.scala | 79 + .../actor/supervisor/SupervisorMiscSpec.scala | 79 + .../actor/supervisor/SupervisorSpec.scala | 638 +++++++ .../scala/akka/dataflow/DataFlowSpec.scala | 165 ++ .../scala/akka/dispatch/ActorModelSpec.scala | 312 ++++ .../scala/akka/dispatch/DispatchersSpec.scala | 70 + ...rBasedEventDrivenDispatcherActorSpec.scala | 138 ++ ...BasedEventDrivenDispatcherActorsSpec.scala | 61 + ...ventDrivenWorkStealingDispatcherSpec.scala | 115 ++ .../test/scala/akka/dispatch/FutureSpec.scala | 146 ++ .../dispatch/HawtDispatcherActorSpec.scala | 71 + .../dispatch/HawtDispatcherEchoServer.scala | 207 +++ .../akka/dispatch/MailboxConfigSpec.scala | 54 + .../akka/dispatch/ThreadBasedActorSpec.scala | 67 + .../dispatch/ThreadBasedDispatcherSpec.scala | 91 + .../test/scala/akka/japi/JavaAPITest.scala | 5 + .../scala/akka/misc/ActorRegistrySpec.scala | 253 +++ .../test/scala/akka/misc/SchedulerSpec.scala | 127 ++ .../test/scala/akka/routing/RoutingSpec.scala | 183 ++ .../scala/akka/ticket/Ticket001Spec.scala | 13 + ...PConsumerPrivateQueueTestIntegration.scala | 45 + .../scala/akka/CamelContextLifecycle.scala | 202 +++ .../src/main/scala/akka/CamelService.scala | 275 +++ akka-camel/src/main/scala/akka/Consumer.scala | 145 ++ .../main/scala/akka/ConsumerPublisher.scala | 351 ++++ akka-camel/src/main/scala/akka/Message.scala | 380 ++++ akka-camel/src/main/scala/akka/Producer.scala | 256 +++ .../scala/akka/component/ActorComponent.scala | 305 ++++ .../akka/component/TypedActorComponent.scala | 111 ++ .../java/akka/camel/ConsumerJavaTestBase.java | 59 + .../camel/SampleErrorHandlingConsumer.java | 34 + .../SampleErrorHandlingTypedConsumer.java | 11 + .../SampleErrorHandlingTypedConsumerImpl.java | 14 + .../camel/SampleRouteDefinitionHandler.java | 14 + .../akka/CamelContextLifecycleTest.scala | 36 + .../scala/akka/CamelExchangeAdapterTest.scala | 109 ++ .../scala/akka/CamelMessageAdapterTest.scala | 38 + .../scala/akka/CamelServiceManagerTest.scala | 62 + .../test/scala/akka/ConsumerJavaTest.scala | 5 + .../scala/akka/ConsumerRegisteredTest.scala | 63 + .../test/scala/akka/ConsumerScalaTest.scala | 271 +++ .../src/test/scala/akka/MessageJavaTest.scala | 5 + .../test/scala/akka/MessageScalaTest.scala | 83 + .../test/scala/akka/ProducerFeatureTest.scala | 301 ++++ .../scala/akka/PublishRequestorTest.scala | 103 ++ .../test/scala/akka/RemoteConsumerTest.scala | 101 ++ .../akka/UntypedProducerFeatureTest.scala | 98 ++ .../component/ActorComponentFeatureTest.scala | 130 ++ .../akka/component/ActorComponentTest.scala | 79 + .../akka/component/ActorProducerTest.scala | 230 +++ .../TypedActorComponentFeatureTest.scala | 108 ++ .../test/scala/akka/support/TestSupport.scala | 81 + .../src/main/scala/akka/AkkaBroadcaster.scala | 40 + .../main/scala/akka/AkkaCometServlet.scala | 101 ++ .../src/main/scala/akka/AkkaLoader.scala | 77 + .../main/scala/akka/DefaultAkkaLoader.scala | 29 + .../main/scala/akka/EmbeddedAppServer.scala | 72 + .../src/main/scala/akka/Initializer.scala | 34 + .../src/main/scala/akka/ListWriter.scala | 41 + akka-http/src/main/scala/akka/Security.scala | 565 ++++++ .../akka/AtomikosTransactionService.scala | 41 + akka-jta/src/main/scala/akka/JTA.scala | 223 +++ .../main/scala/akka/TransactionContext.scala | 238 +++ .../main/scala/akka/TransactionProtocol.scala | 227 +++ akka-kernel/src/main/scala/akka/Kernel.scala | 25 + .../main/scala/akka/CassandraSession.scala | 199 +++ .../main/scala/akka/CassandraStorage.scala | 59 + .../scala/akka/CassandraStorageBackend.scala | 161 ++ .../scala/akka/CommonStorageBackend.scala | 741 ++++++++ .../src/main/scala/akka/Pool.scala | 91 + .../src/main/scala/akka/Storage.scala | 876 ++++++++++ .../src/main/scala/akka/StorageBackend.scala | 79 + .../src/main/scala/akka/CouchDBStorage.scala | 47 + .../scala/akka/CouchDBStorageBackend.scala | 210 +++ .../src/main/scala/akka/HbaseStorage.scala | 50 + .../main/scala/akka/HbaseStorageBackend.scala | 254 +++ .../main/scala/akka/MemcachedStorage.scala | 51 + .../scala/akka/MemcachedStorageBackend.scala | 117 ++ .../src/main/scala/akka/MongoStorage.scala | 51 + .../main/scala/akka/MongoStorageBackend.scala | 229 +++ .../main/scala/akka/RedisPubSubServer.scala | 42 + .../src/main/scala/akka/RedisStorage.scala | 80 + .../main/scala/akka/RedisStorageBackend.scala | 362 ++++ .../java/akka/persistence/redis/ChatLog.java | 24 + .../akka/persistence/redis/ChatMessage.java | 21 + .../java/akka/persistence/redis/Event.java | 12 + .../akka/persistence/redis/GetChatLog.java | 15 + .../persistence/redis/RedisChatStorage.java | 53 + .../persistence/redis/RedisStorageTests.java | 34 + .../src/test/scala/RedisStorageSpec.scala | 5 + .../src/test/scala/RedisTicket513Spec.scala | 70 + .../src/main/scala/akka/RiakStorage.scala | 51 + .../main/scala/akka/RiakStorageBackend.scala | 123 ++ .../src/main/scala/akka/SimpledbStorage.scala | 51 + .../scala/akka/SimpledbStorageBackend.scala | 295 ++++ ...pledbStorageBackendCompatibilityTest.scala | 49 + .../test/scala/SimpledbTestIntegration.scala | 52 + .../SimpledbTicket343TestIntegration.scala | 23 + .../main/scala/akka/VoldemortStorage.scala | 51 + .../scala/akka/VoldemortStorageBackend.scala | 146 ++ .../remote/BootableRemoteActorService.scala | 44 + .../scala/akka/remote/MessageSerializer.scala | 110 ++ .../main/scala/akka/remote/RemoteClient.scala | 509 ++++++ .../main/scala/akka/remote/RemoteServer.scala | 764 ++++++++ .../scala/akka/serialization/Binary.scala | 320 ++++ .../akka/serialization/Compression.scala | 21 + .../akka/serialization/Serializable.scala | 121 ++ .../serialization/SerializationProtocol.scala | 426 +++++ .../scala/akka/serialization/Serializer.scala | 179 ++ .../scala/akka/serialization/package.scala | 9 + .../src/test/scala/ticket/Ticket519Spec.scala | 30 + .../src/main/scala/SimpleService.scala | 208 +++ .../akka/ActorBeanDefinitionParser.scala | 93 + .../main/scala/akka/ActorFactoryBean.scala | 265 +++ .../src/main/scala/akka/ActorParser.scala | 231 +++ .../src/main/scala/akka/ActorProperties.scala | 78 + .../scala/akka/AkkaNamespaceHandler.scala | 23 + .../akka/AkkaSpringConfigurationTags.scala | 115 ++ .../CamelServiceBeanDefinitionParser.scala | 41 + .../scala/akka/CamelServiceFactoryBean.scala | 45 + ...onfiggyPropertyPlaceholderConfigurer.scala | 37 + .../akka/DispatcherBeanDefinitionParser.scala | 28 + .../scala/akka/DispatcherFactoryBean.scala | 110 ++ .../scala/akka/DispatcherProperties.scala | 61 + .../src/main/scala/akka/PropertyEntries.scala | 36 + .../src/main/scala/akka/StringReflect.scala | 25 + .../SupervisionBeanDefinitionParser.scala | 86 + .../scala/akka/SupervisionFactoryBean.scala | 97 ++ .../src/main/scala/akka/agent/Agent.scala | 244 +++ akka-stm/src/main/scala/akka/stm/Atomic.scala | 40 + akka-stm/src/main/scala/akka/stm/Ref.scala | 124 ++ akka-stm/src/main/scala/akka/stm/Stm.scala | 149 ++ .../src/main/scala/akka/stm/Transaction.scala | 232 +++ .../scala/akka/stm/TransactionFactory.scala | 225 +++ .../akka/stm/TransactionFactoryBuilder.scala | 89 + .../scala/akka/stm/TransactionalMap.scala | 89 + .../scala/akka/stm/TransactionalVector.scala | 66 + .../src/main/scala/akka/stm/package.scala | 41 + .../scala/akka/transactor/Atomically.scala | 21 + .../scala/akka/transactor/Coordinated.scala | 165 ++ .../scala/akka/transactor/Transactor.scala | 179 ++ .../akka/transactor/UntypedTransactor.scala | 110 ++ .../test/java/akka/stm/example/Address.java | 13 + .../java/akka/stm/example/CounterExample.java | 25 + .../java/akka/stm/example/RefExample.java | 35 + .../java/akka/stm/example/StmExamples.java | 15 + .../example/TransactionFactoryExample.java | 29 + .../stm/example/TransactionalMapExample.java | 34 + .../example/TransactionalVectorExample.java | 33 + .../src/test/java/akka/stm/example/User.java | 13 + .../test/java/akka/stm/test/JavaStmTests.java | 90 + .../akka/transactor/example/Increment.java | 21 + .../example/UntypedCoordinatedCounter.java | 39 + .../example/UntypedCoordinatedExample.java | 44 + .../transactor/example/UntypedCounter.java | 33 + .../example/UntypedTransactorExample.java | 43 + .../java/akka/transactor/test/Increment.java | 23 + .../test/UntypedCoordinatedCounter.java | 63 + .../test/UntypedCoordinatedIncrementTest.java | 88 + .../akka/transactor/test/UntypedCounter.java | 77 + .../akka/transactor/test/UntypedFailer.java | 9 + .../test/UntypedTransactorTest.java | 87 + akka-stm/src/test/scala/agent/AgentSpec.scala | 170 ++ akka-stm/src/test/scala/stm/JavaStmSpec.scala | 5 + akka-stm/src/test/scala/stm/RefSpec.scala | 152 ++ akka-stm/src/test/scala/stm/StmSpec.scala | 129 ++ .../transactor/CoordinatedIncrementSpec.scala | 86 + .../scala/transactor/FickleFriendsSpec.scala | 122 ++ .../JavaUntypedCoordinatedSpec.scala | 8 + .../JavaUntypedTransactorSpec.scala | 8 + .../scala/transactor/TransactorSpec.scala | 100 ++ .../transactor/annotation/Coordinated.java | 7 + .../main/scala/akka/actor/TypedActor.scala | 952 ++++++++++ .../akka/config/TypedActorConfigurator.scala | 82 + .../config/TypedActorGuiceConfigurator.scala | 183 ++ .../scala/akka/transactor/Coordination.scala | 88 + .../src/test/java/akka/actor/Bar.java | 6 + .../src/test/java/akka/actor/BarImpl.java | 16 + .../src/test/java/akka/actor/Ext.java | 6 + .../src/test/java/akka/actor/ExtImpl.java | 6 + .../src/test/java/akka/actor/Foo.java | 14 + .../src/test/java/akka/actor/FooImpl.java | 40 + .../src/test/java/akka/actor/SamplePojo.java | 9 + .../test/java/akka/actor/SamplePojoImpl.java | 49 + .../test/java/akka/actor/SimpleJavaPojo.java | 16 + .../java/akka/actor/SimpleJavaPojoCaller.java | 9 + .../akka/actor/SimpleJavaPojoCallerImpl.java | 26 + .../java/akka/actor/SimpleJavaPojoImpl.java | 58 + .../java/akka/actor/TypedActorFailer.java | 5 + .../java/akka/actor/TypedActorFailerImpl.java | 9 + .../java/akka/transactor/test/FailerImpl.java | 16 + .../test/TypedCoordinatedIncrementTest.java | 69 + .../akka/transactor/test/TypedCounter.java | 8 + .../transactor/test/TypedCounterImpl.java | 16 + .../JavaCoordinatedIncrementSpec.scala | 8 + .../TypedCoordinatedIncrementSpec.scala | 72 + .../1.0.14/aws-java-sdk-1.0.14-javadoc.jar | Bin 0 -> 3451189 bytes .../1.0.14/aws-java-sdk-1.0.14-sources.jar | Bin 0 -> 2107436 bytes .../1.0.14/aws-java-sdk-1.0.14.jar | Bin 0 -> 2038339 bytes 247 files changed, 30692 insertions(+) create mode 100644 akka-actor/src/main/scala/akka/actor/Actor.scala create mode 100644 akka-actor/src/main/scala/akka/actor/ActorRef.scala create mode 100644 akka-actor/src/main/scala/akka/actor/ActorRegistry.scala create mode 100644 akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala create mode 100644 akka-actor/src/main/scala/akka/actor/FSM.scala create mode 100644 akka-actor/src/main/scala/akka/actor/Implicits.scala create mode 100644 akka-actor/src/main/scala/akka/actor/Scheduler.scala create mode 100644 akka-actor/src/main/scala/akka/actor/Supervisor.scala create mode 100644 akka-actor/src/main/scala/akka/actor/UntypedActor.scala create mode 100644 akka-actor/src/main/scala/akka/config/Config.scala create mode 100644 akka-actor/src/main/scala/akka/config/Configuration.scala create mode 100644 akka-actor/src/main/scala/akka/config/Configurator.scala create mode 100644 akka-actor/src/main/scala/akka/config/SupervisionConfig.scala create mode 100644 akka-actor/src/main/scala/akka/dataflow/DataFlowVariable.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/Future.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/HawtDispatcher.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/MailboxHandling.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/ThreadBasedDispatcher.scala create mode 100644 akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala create mode 100644 akka-actor/src/main/scala/akka/japi/JavaAPI.scala create mode 100644 akka-actor/src/main/scala/akka/routing/Iterators.scala create mode 100644 akka-actor/src/main/scala/akka/routing/Listeners.scala create mode 100644 akka-actor/src/main/scala/akka/routing/Routers.scala create mode 100644 akka-actor/src/main/scala/akka/routing/Routing.scala create mode 100644 akka-actor/src/main/scala/akka/util/Address.scala create mode 100644 akka-actor/src/main/scala/akka/util/AkkaException.scala create mode 100644 akka-actor/src/main/scala/akka/util/Bootable.scala create mode 100644 akka-actor/src/main/scala/akka/util/Crypt.scala create mode 100644 akka-actor/src/main/scala/akka/util/Duration.scala create mode 100644 akka-actor/src/main/scala/akka/util/HashCode.scala create mode 100644 akka-actor/src/main/scala/akka/util/Helpers.scala create mode 100644 akka-actor/src/main/scala/akka/util/ListenerManagement.scala create mode 100644 akka-actor/src/main/scala/akka/util/LockUtil.scala create mode 100644 akka-actor/src/main/scala/akka/util/Logging.scala create mode 100644 akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala create mode 100644 akka-actor/src/test/scala/akka/Messages.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/ActorFireForgetRequestReplySpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/ActorRefSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/Bench.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/ForwardActorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/HotSwapSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala create mode 100644 akka-actor/src/test/scala/akka/actor/supervisor/SupervisorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dataflow/DataFlowSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ActorModelSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/DispatchersSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/FutureSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/HawtDispatcherActorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/HawtDispatcherEchoServer.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/MailboxConfigSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala create mode 100644 akka-actor/src/test/scala/akka/dispatch/ThreadBasedDispatcherSpec.scala create mode 100644 akka-actor/src/test/scala/akka/japi/JavaAPITest.scala create mode 100644 akka-actor/src/test/scala/akka/misc/ActorRegistrySpec.scala create mode 100644 akka-actor/src/test/scala/akka/misc/SchedulerSpec.scala create mode 100644 akka-actor/src/test/scala/akka/routing/RoutingSpec.scala create mode 100644 akka-actor/src/test/scala/akka/ticket/Ticket001Spec.scala create mode 100644 akka-amqp/src/test/scala/akka/amqp/test/AMQPConsumerPrivateQueueTestIntegration.scala create mode 100644 akka-camel/src/main/scala/akka/CamelContextLifecycle.scala create mode 100644 akka-camel/src/main/scala/akka/CamelService.scala create mode 100644 akka-camel/src/main/scala/akka/Consumer.scala create mode 100644 akka-camel/src/main/scala/akka/ConsumerPublisher.scala create mode 100644 akka-camel/src/main/scala/akka/Message.scala create mode 100644 akka-camel/src/main/scala/akka/Producer.scala create mode 100644 akka-camel/src/main/scala/akka/component/ActorComponent.scala create mode 100644 akka-camel/src/main/scala/akka/component/TypedActorComponent.scala create mode 100644 akka-camel/src/test/java/akka/camel/ConsumerJavaTestBase.java create mode 100644 akka-camel/src/test/java/akka/camel/SampleErrorHandlingConsumer.java create mode 100644 akka-camel/src/test/java/akka/camel/SampleErrorHandlingTypedConsumer.java create mode 100644 akka-camel/src/test/java/akka/camel/SampleErrorHandlingTypedConsumerImpl.java create mode 100644 akka-camel/src/test/java/akka/camel/SampleRouteDefinitionHandler.java create mode 100644 akka-camel/src/test/scala/akka/CamelContextLifecycleTest.scala create mode 100644 akka-camel/src/test/scala/akka/CamelExchangeAdapterTest.scala create mode 100644 akka-camel/src/test/scala/akka/CamelMessageAdapterTest.scala create mode 100644 akka-camel/src/test/scala/akka/CamelServiceManagerTest.scala create mode 100644 akka-camel/src/test/scala/akka/ConsumerJavaTest.scala create mode 100644 akka-camel/src/test/scala/akka/ConsumerRegisteredTest.scala create mode 100644 akka-camel/src/test/scala/akka/ConsumerScalaTest.scala create mode 100644 akka-camel/src/test/scala/akka/MessageJavaTest.scala create mode 100644 akka-camel/src/test/scala/akka/MessageScalaTest.scala create mode 100644 akka-camel/src/test/scala/akka/ProducerFeatureTest.scala create mode 100644 akka-camel/src/test/scala/akka/PublishRequestorTest.scala create mode 100644 akka-camel/src/test/scala/akka/RemoteConsumerTest.scala create mode 100644 akka-camel/src/test/scala/akka/UntypedProducerFeatureTest.scala create mode 100644 akka-camel/src/test/scala/akka/component/ActorComponentFeatureTest.scala create mode 100644 akka-camel/src/test/scala/akka/component/ActorComponentTest.scala create mode 100644 akka-camel/src/test/scala/akka/component/ActorProducerTest.scala create mode 100644 akka-camel/src/test/scala/akka/component/TypedActorComponentFeatureTest.scala create mode 100644 akka-camel/src/test/scala/akka/support/TestSupport.scala create mode 100644 akka-http/src/main/scala/akka/AkkaBroadcaster.scala create mode 100644 akka-http/src/main/scala/akka/AkkaCometServlet.scala create mode 100644 akka-http/src/main/scala/akka/AkkaLoader.scala create mode 100644 akka-http/src/main/scala/akka/DefaultAkkaLoader.scala create mode 100644 akka-http/src/main/scala/akka/EmbeddedAppServer.scala create mode 100644 akka-http/src/main/scala/akka/Initializer.scala create mode 100644 akka-http/src/main/scala/akka/ListWriter.scala create mode 100644 akka-http/src/main/scala/akka/Security.scala create mode 100644 akka-jta/src/main/scala/akka/AtomikosTransactionService.scala create mode 100644 akka-jta/src/main/scala/akka/JTA.scala create mode 100644 akka-jta/src/main/scala/akka/TransactionContext.scala create mode 100644 akka-jta/src/main/scala/akka/TransactionProtocol.scala create mode 100644 akka-kernel/src/main/scala/akka/Kernel.scala create mode 100644 akka-persistence/akka-persistence-cassandra/src/main/scala/akka/CassandraSession.scala create mode 100644 akka-persistence/akka-persistence-cassandra/src/main/scala/akka/CassandraStorage.scala create mode 100644 akka-persistence/akka-persistence-cassandra/src/main/scala/akka/CassandraStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-common/src/main/scala/akka/CommonStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-common/src/main/scala/akka/Pool.scala create mode 100644 akka-persistence/akka-persistence-common/src/main/scala/akka/Storage.scala create mode 100644 akka-persistence/akka-persistence-common/src/main/scala/akka/StorageBackend.scala create mode 100644 akka-persistence/akka-persistence-couchdb/src/main/scala/akka/CouchDBStorage.scala create mode 100644 akka-persistence/akka-persistence-couchdb/src/main/scala/akka/CouchDBStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-hbase/src/main/scala/akka/HbaseStorage.scala create mode 100644 akka-persistence/akka-persistence-hbase/src/main/scala/akka/HbaseStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-memcached/src/main/scala/akka/MemcachedStorage.scala create mode 100644 akka-persistence/akka-persistence-memcached/src/main/scala/akka/MemcachedStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-mongo/src/main/scala/akka/MongoStorage.scala create mode 100644 akka-persistence/akka-persistence-mongo/src/main/scala/akka/MongoStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-redis/src/main/scala/akka/RedisPubSubServer.scala create mode 100644 akka-persistence/akka-persistence-redis/src/main/scala/akka/RedisStorage.scala create mode 100644 akka-persistence/akka-persistence-redis/src/main/scala/akka/RedisStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/ChatLog.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/ChatMessage.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/Event.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/GetChatLog.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/RedisChatStorage.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/java/akka/persistence/redis/RedisStorageTests.java create mode 100644 akka-persistence/akka-persistence-redis/src/test/scala/RedisStorageSpec.scala create mode 100644 akka-persistence/akka-persistence-redis/src/test/scala/RedisTicket513Spec.scala create mode 100644 akka-persistence/akka-persistence-riak/src/main/scala/akka/RiakStorage.scala create mode 100644 akka-persistence/akka-persistence-riak/src/main/scala/akka/RiakStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-simpledb/src/main/scala/akka/SimpledbStorage.scala create mode 100644 akka-persistence/akka-persistence-simpledb/src/main/scala/akka/SimpledbStorageBackend.scala create mode 100644 akka-persistence/akka-persistence-simpledb/src/test/scala/SimpledbStorageBackendCompatibilityTest.scala create mode 100644 akka-persistence/akka-persistence-simpledb/src/test/scala/SimpledbTestIntegration.scala create mode 100644 akka-persistence/akka-persistence-simpledb/src/test/scala/SimpledbTicket343TestIntegration.scala create mode 100644 akka-persistence/akka-persistence-voldemort/src/main/scala/akka/VoldemortStorage.scala create mode 100644 akka-persistence/akka-persistence-voldemort/src/main/scala/akka/VoldemortStorageBackend.scala create mode 100644 akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala create mode 100644 akka-remote/src/main/scala/akka/remote/MessageSerializer.scala create mode 100644 akka-remote/src/main/scala/akka/remote/RemoteClient.scala create mode 100644 akka-remote/src/main/scala/akka/remote/RemoteServer.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/Binary.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/Compression.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/Serializable.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/Serializer.scala create mode 100644 akka-remote/src/main/scala/akka/serialization/package.scala create mode 100644 akka-remote/src/test/scala/ticket/Ticket519Spec.scala create mode 100644 akka-samples/akka-sample-rest-scala/src/main/scala/SimpleService.scala create mode 100644 akka-spring/src/main/scala/akka/ActorBeanDefinitionParser.scala create mode 100644 akka-spring/src/main/scala/akka/ActorFactoryBean.scala create mode 100644 akka-spring/src/main/scala/akka/ActorParser.scala create mode 100644 akka-spring/src/main/scala/akka/ActorProperties.scala create mode 100644 akka-spring/src/main/scala/akka/AkkaNamespaceHandler.scala create mode 100644 akka-spring/src/main/scala/akka/AkkaSpringConfigurationTags.scala create mode 100644 akka-spring/src/main/scala/akka/CamelServiceBeanDefinitionParser.scala create mode 100644 akka-spring/src/main/scala/akka/CamelServiceFactoryBean.scala create mode 100644 akka-spring/src/main/scala/akka/ConfiggyPropertyPlaceholderConfigurer.scala create mode 100644 akka-spring/src/main/scala/akka/DispatcherBeanDefinitionParser.scala create mode 100644 akka-spring/src/main/scala/akka/DispatcherFactoryBean.scala create mode 100644 akka-spring/src/main/scala/akka/DispatcherProperties.scala create mode 100644 akka-spring/src/main/scala/akka/PropertyEntries.scala create mode 100644 akka-spring/src/main/scala/akka/StringReflect.scala create mode 100644 akka-spring/src/main/scala/akka/SupervisionBeanDefinitionParser.scala create mode 100644 akka-spring/src/main/scala/akka/SupervisionFactoryBean.scala create mode 100644 akka-stm/src/main/scala/akka/agent/Agent.scala create mode 100644 akka-stm/src/main/scala/akka/stm/Atomic.scala create mode 100644 akka-stm/src/main/scala/akka/stm/Ref.scala create mode 100644 akka-stm/src/main/scala/akka/stm/Stm.scala create mode 100644 akka-stm/src/main/scala/akka/stm/Transaction.scala create mode 100644 akka-stm/src/main/scala/akka/stm/TransactionFactory.scala create mode 100644 akka-stm/src/main/scala/akka/stm/TransactionFactoryBuilder.scala create mode 100644 akka-stm/src/main/scala/akka/stm/TransactionalMap.scala create mode 100644 akka-stm/src/main/scala/akka/stm/TransactionalVector.scala create mode 100644 akka-stm/src/main/scala/akka/stm/package.scala create mode 100644 akka-stm/src/main/scala/akka/transactor/Atomically.scala create mode 100644 akka-stm/src/main/scala/akka/transactor/Coordinated.scala create mode 100644 akka-stm/src/main/scala/akka/transactor/Transactor.scala create mode 100644 akka-stm/src/main/scala/akka/transactor/UntypedTransactor.scala create mode 100644 akka-stm/src/test/java/akka/stm/example/Address.java create mode 100644 akka-stm/src/test/java/akka/stm/example/CounterExample.java create mode 100644 akka-stm/src/test/java/akka/stm/example/RefExample.java create mode 100644 akka-stm/src/test/java/akka/stm/example/StmExamples.java create mode 100644 akka-stm/src/test/java/akka/stm/example/TransactionFactoryExample.java create mode 100644 akka-stm/src/test/java/akka/stm/example/TransactionalMapExample.java create mode 100644 akka-stm/src/test/java/akka/stm/example/TransactionalVectorExample.java create mode 100644 akka-stm/src/test/java/akka/stm/example/User.java create mode 100644 akka-stm/src/test/java/akka/stm/test/JavaStmTests.java create mode 100644 akka-stm/src/test/java/akka/transactor/example/Increment.java create mode 100644 akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java create mode 100644 akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java create mode 100644 akka-stm/src/test/java/akka/transactor/example/UntypedCounter.java create mode 100644 akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/Increment.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/UntypedCounter.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/UntypedFailer.java create mode 100644 akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java create mode 100644 akka-stm/src/test/scala/agent/AgentSpec.scala create mode 100644 akka-stm/src/test/scala/stm/JavaStmSpec.scala create mode 100644 akka-stm/src/test/scala/stm/RefSpec.scala create mode 100644 akka-stm/src/test/scala/stm/StmSpec.scala create mode 100644 akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala create mode 100644 akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala create mode 100644 akka-stm/src/test/scala/transactor/JavaUntypedCoordinatedSpec.scala create mode 100644 akka-stm/src/test/scala/transactor/JavaUntypedTransactorSpec.scala create mode 100644 akka-stm/src/test/scala/transactor/TransactorSpec.scala create mode 100644 akka-typed-actor/src/main/java/akka/transactor/annotation/Coordinated.java create mode 100644 akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala create mode 100644 akka-typed-actor/src/main/scala/akka/config/TypedActorConfigurator.scala create mode 100644 akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala create mode 100644 akka-typed-actor/src/main/scala/akka/transactor/Coordination.scala create mode 100644 akka-typed-actor/src/test/java/akka/actor/Bar.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/BarImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/Ext.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/ExtImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/Foo.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/FooImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SamplePojo.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SamplePojoImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SimpleJavaPojo.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SimpleJavaPojoCaller.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SimpleJavaPojoCallerImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/SimpleJavaPojoImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/TypedActorFailer.java create mode 100644 akka-typed-actor/src/test/java/akka/actor/TypedActorFailerImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/transactor/test/FailerImpl.java create mode 100644 akka-typed-actor/src/test/java/akka/transactor/test/TypedCoordinatedIncrementTest.java create mode 100644 akka-typed-actor/src/test/java/akka/transactor/test/TypedCounter.java create mode 100644 akka-typed-actor/src/test/java/akka/transactor/test/TypedCounterImpl.java create mode 100644 akka-typed-actor/src/test/scala/transactor/JavaCoordinatedIncrementSpec.scala create mode 100644 akka-typed-actor/src/test/scala/transactor/TypedCoordinatedIncrementSpec.scala create mode 100644 embedded-repo/com/amazonaws/aws-java-sdk/1.0.14/aws-java-sdk-1.0.14-javadoc.jar create mode 100644 embedded-repo/com/amazonaws/aws-java-sdk/1.0.14/aws-java-sdk-1.0.14-sources.jar create mode 100644 embedded-repo/com/amazonaws/aws-java-sdk/1.0.14/aws-java-sdk-1.0.14.jar diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala new file mode 100644 index 0000000000..8a7b045475 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -0,0 +1,505 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package akka.actor + +import akka.dispatch._ +import akka.config.Config._ +import akka.config.Supervision._ +import akka.util.Helpers.{narrow, narrowSilently} +import akka.AkkaException + +import java.util.concurrent.TimeUnit +import java.net.InetSocketAddress + +import scala.reflect.BeanProperty +import akka.util. {ReflectiveAccess, Logging, Duration} +import akka.japi.Procedure + +/** + * Extend this abstract class to create a remote actor. + *

+ * Equivalent to invoking the makeRemote(..) method in the body of the ActorJonas Bonér + */ +abstract class RemoteActor(address: InetSocketAddress) extends Actor { + def this(hostname: String, port: Int) = this(new InetSocketAddress(hostname, port)) + self.makeRemote(address) +} + +/** + * Life-cycle messages for the Actors + */ +@serializable sealed trait LifeCycleMessage + +case class HotSwap(code: ActorRef => Actor.Receive) extends LifeCycleMessage { + /** + * Java API + */ + def this(code: akka.japi.Function[ActorRef,Procedure[Any]]) = + this( (self: ActorRef) => { + val behavior = code(self) + val result: Actor.Receive = { case msg => behavior(msg) } + result + }) +} + +case object RevertHotSwap extends LifeCycleMessage + +case class Restart(reason: Throwable) extends LifeCycleMessage + +case class Exit(dead: ActorRef, killer: Throwable) extends LifeCycleMessage + +case class Link(child: ActorRef) extends LifeCycleMessage + +case class Unlink(child: ActorRef) extends LifeCycleMessage + +case class UnlinkAndStop(child: ActorRef) extends LifeCycleMessage + +case object ReceiveTimeout extends LifeCycleMessage + +case class MaximumNumberOfRestartsWithinTimeRangeReached( + @BeanProperty val victim: ActorRef, + @BeanProperty val maxNrOfRetries: Option[Int], + @BeanProperty val withinTimeRange: Option[Int], + @BeanProperty val lastExceptionCausingRestart: Throwable) extends LifeCycleMessage + +// Exceptions for Actors +class ActorStartException private[akka](message: String) extends AkkaException(message) +class IllegalActorStateException private[akka](message: String) extends AkkaException(message) +class ActorKilledException private[akka](message: String) extends AkkaException(message) +class ActorInitializationException private[akka](message: String) extends AkkaException(message) +class ActorTimeoutException private[akka](message: String) extends AkkaException(message) + +/** + * This message is thrown by default when an Actors behavior doesn't match a message + */ +case class UnhandledMessageException(msg: Any, ref: ActorRef) extends Exception { + override def getMessage() = "Actor %s does not handle [%s]".format(ref,msg) + override def fillInStackTrace() = this //Don't waste cycles generating stack trace +} + +/** + * Actor factory module with factory methods for creating various kinds of Actors. + * + * @author Jonas Bonér + */ +object Actor extends Logging { + + /** + * Add shutdown cleanups + */ + private[akka] lazy val shutdownHook = { + val hook = new Runnable { + override def run { + // Shutdown HawtDispatch GlobalQueue + log.info("Shutting down Hawt Dispatch global queue") + org.fusesource.hawtdispatch.ScalaDispatch.globalQueue.asInstanceOf[org.fusesource.hawtdispatch.internal.GlobalDispatchQueue].shutdown + + // Clear Thread.subclassAudits + log.info("Clearing subclass audits") + val tf = classOf[java.lang.Thread].getDeclaredField("subclassAudits") + tf.setAccessible(true) + val subclassAudits = tf.get(null).asInstanceOf[java.util.Map[_,_]] + subclassAudits.synchronized {subclassAudits.clear} + + // Clear and reset j.u.l.Level.known (due to Configgy) + log.info("Removing Configgy-installed log levels") + import java.util.logging.Level + val lf = classOf[Level].getDeclaredField("known") + lf.setAccessible(true) + val known = lf.get(null).asInstanceOf[java.util.ArrayList[Level]] + known.synchronized { + known.clear + List(Level.OFF,Level.SEVERE,Level.WARNING,Level.INFO,Level.CONFIG, + Level.FINE,Level.FINER,Level.FINEST,Level.ALL) foreach known.add + } + } + } + Runtime.getRuntime.addShutdownHook(new Thread(hook)) + hook + } + + val TIMEOUT = Duration(config.getInt("akka.actor.timeout", 5), TIME_UNIT).toMillis + val SERIALIZE_MESSAGES = config.getBool("akka.actor.serialize-messages", false) + + /** + * A Receive is a convenience type that defines actor message behavior currently modeled as + * a PartialFunction[Any, Unit]. + */ + type Receive = PartialFunction[Any, Unit] + + private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None) + + /** + * Creates an ActorRef out of the Actor with type T. + *

+   *   import Actor._
+   *   val actor = actorOf[MyActor]
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor].start
+   * 
+ */ + def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + + /** + * Creates an ActorRef out of the Actor with type T. + *
+   *   import Actor._
+   *   val actor = actorOf[MyActor]
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor].start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) + }) + + /** + * Creates an ActorRef out of the Actor. Allows you to pass in a factory function + * that creates the Actor. Please note that this function can be invoked multiple + * times if for example the Actor is supervised and needs to be restarted. + *

+ * This function should NOT be used for remote actors. + *

+   *   import Actor._
+   *   val actor = actorOf(new MyActor)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(new MyActor).start
+   * 
+ */ + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) + + /** + * Use to spawn out a block of code in an event-driven actor. Will shut actor down when + * the block has been executed. + *

+ * NOTE: If used from within an Actor then has to be qualified with 'Actor.spawn' since + * there is a method 'spawn[ActorType]' in the Actor trait already. + * Example: + *

+   * import Actor._
+   *
+   * spawn  {
+   *   ... // do stuff
+   * }
+   * 
+ */ + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { + case object Spawn + actorOf(new Actor() { + self.dispatcher = dispatcher + def receive = { + case Spawn => try { body } finally { self.stop } + } + }).start ! Spawn + } + + /** + * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] + * to convert an Option[Any] to an Option[T]. + */ + implicit def toAnyOptionAsTypedOption(anyOption: Option[Any]) = new AnyOptionAsTypedOption(anyOption) +} + +/** + * Actor base trait that should be extended by or mixed to create an Actor with the semantics of the 'Actor Model': + * http://en.wikipedia.org/wiki/Actor_model + *

+ * An actor has a well-defined (non-cyclic) life-cycle. + *

+ * => NEW (newly created actor) - can't receive messages (yet)
+ *     => STARTED (when 'start' is invoked) - can receive messages
+ *         => SHUT DOWN (when 'exit' is invoked) - can't do anything
+ * 
+ * + *

+ * The Actor's API is available in the 'self' member variable. + * + *

+ * Here you find functions like: + * - !, !!, !!! and forward + * - link, unlink, startLink, spawnLink etc + * - makeRemote etc. + * - start, stop + * - etc. + * + *

+ * Here you also find fields like + * - dispatcher = ... + * - id = ... + * - lifeCycle = ... + * - faultHandler = ... + * - trapExit = ... + * - etc. + * + *

+ * This means that to use them you have to prefix them with 'self', like this: self ! Message + * + * However, for convenience you can import these functions and fields like below, which will allow you do + * drop the 'self' prefix: + *

+ * class MyActor extends Actor  {
+ *   import self._
+ *   id = ...
+ *   dispatcher = ...
+ *   spawnLink[OtherActor]
+ *   ...
+ * }
+ * 
+ * + *

+ * The Actor trait also has a 'log' member field that can be used for logging within the Actor. + * + * @author Jonas Bonér + */ +trait Actor extends Logging { + + /** + * Type alias because traits cannot have companion objects. + */ + type Receive = Actor.Receive + + /* + * Some[ActorRef] representation of the 'self' ActorRef reference. + *

+ * Mainly for internal use, functions as the implicit sender references when invoking + * the 'forward' function. + */ + @transient implicit val someSelf: Some[ActorRef] = { + val optRef = Actor.actorRefInCreation.value + if (optRef.isEmpty) throw new ActorInitializationException( + "ActorRef for instance of actor [" + getClass.getName + "] is not in scope." + + "\n\tYou can not create an instance of an actor explicitly using 'new MyActor'." + + "\n\tYou have to use one of the factory methods in the 'Actor' object to create a new actor." + + "\n\tEither use:" + + "\n\t\t'val actor = Actor.actorOf[MyActor]', or" + + "\n\t\t'val actor = Actor.actorOf(new MyActor(..))', or" + + "\n\t\t'val actor = Actor.actor { case msg => .. } }'") + val ref = optRef.asInstanceOf[Some[ActorRef]].get + ref.id = getClass.getName //FIXME: Is this needed? + optRef.asInstanceOf[Some[ActorRef]] + } + + /* + * Option[ActorRef] representation of the 'self' ActorRef reference. + *

+ * Mainly for internal use, functions as the implicit sender references when invoking + * one of the message send functions ('!', '!!' and '!!!'). + */ + implicit def optionSelf: Option[ActorRef] = someSelf + + /** + * The 'self' field holds the ActorRef for this actor. + *

+ * Can be used to send messages to itself: + *

+   * self ! message
+   * 
+ * Here you also find most of the Actor API. + *

+ * For example fields like: + *

+   * self.dispactcher = ...
+   * self.trapExit = ...
+   * self.faultHandler = ...
+   * self.lifeCycle = ...
+   * self.sender
+   * 
+ *

+ * Here you also find methods like: + *

+   * self.reply(..)
+   * self.link(..)
+   * self.unlink(..)
+   * self.start(..)
+   * self.stop(..)
+   * 
+ */ + @transient val self: ScalaActorRef = someSelf.get + + /** + * User overridable callback/setting. + *

+ * Partial function implementing the actor logic. + * To be implemented by concrete actor class. + *

+ * Example code: + *

+   *   def receive =  {
+   *     case Ping =>
+   *       log.info("got a 'Ping' message")
+   *       self.reply("pong")
+   *
+   *     case OneWay =>
+   *       log.info("got a 'OneWay' message")
+   *
+   *     case unknown =>
+   *       log.warning("unknown message [%s], ignoring", unknown)
+   * }
+   * 
+ */ + protected def receive: Receive + + /** + * User overridable callback. + *

+ * Is called when an Actor is started by invoking 'actor.start'. + */ + def preStart {} + + /** + * User overridable callback. + *

+ * Is called when 'actor.stop' is invoked. + */ + def postStop {} + + /** + * User overridable callback. + *

+ * Is called on a crashed Actor right BEFORE it is restarted to allow clean up of resources before Actor is terminated. + */ + def preRestart(reason: Throwable) {} + + /** + * User overridable callback. + *

+ * Is called right AFTER restart on the newly created Actor to allow reinitialization after an Actor crash. + */ + def postRestart(reason: Throwable) {} + + /** + * User overridable callback. + *

+ * Is called when a message isn't handled by the current behavior of the actor + * by default it throws an UnhandledMessageException + */ + def unhandled(msg: Any){ + throw new UnhandledMessageException(msg,self) + } + + /** + * Is the actor able to handle the message passed in as arguments? + */ + def isDefinedAt(message: Any): Boolean = processingBehavior.isDefinedAt(message) + + /** + * Changes tha Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler. + * Puts the behavior on top of the hotswap stack. + * If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack + */ + def become(behavior: Receive, discardOld: Boolean = false) { + if (discardOld) + unbecome + + self.hotswap = self.hotswap.push(behavior) + } + + /** + * Reverts the Actor behavior to the previous one in the hotswap stack. + */ + def unbecome: Unit = if (!self.hotswap.isEmpty) self.hotswap = self.hotswap.pop + + // ========================================= + // ==== INTERNAL IMPLEMENTATION DETAILS ==== + // ========================================= + + private[akka] def apply(msg: Any) = fullBehavior(msg) + + /*Processingbehavior and fullBehavior are duplicates so make sure changes are done to both */ + private lazy val processingBehavior: Receive = { + lazy val defaultBehavior = receive + val actorBehavior: Receive = { + case HotSwap(code) => become(code(self)) + case RevertHotSwap => unbecome + case Exit(dead, reason) => self.handleTrapExit(dead, reason) + case Link(child) => self.link(child) + case Unlink(child) => self.unlink(child) + case UnlinkAndStop(child) => self.unlink(child); child.stop + case Restart(reason) => throw reason + case msg if !self.hotswap.isEmpty && + self.hotswap.head.isDefinedAt(msg) => self.hotswap.head.apply(msg) + case msg if self.hotswap.isEmpty && + defaultBehavior.isDefinedAt(msg) => defaultBehavior.apply(msg) + } + actorBehavior + } + + private lazy val fullBehavior: Receive = { + lazy val defaultBehavior = receive + val actorBehavior: Receive = { + case HotSwap(code) => become(code(self)) + case RevertHotSwap => unbecome + case Exit(dead, reason) => self.handleTrapExit(dead, reason) + case Link(child) => self.link(child) + case Unlink(child) => self.unlink(child) + case UnlinkAndStop(child) => self.unlink(child); child.stop + case Restart(reason) => throw reason + case msg if !self.hotswap.isEmpty && + self.hotswap.head.isDefinedAt(msg) => self.hotswap.head.apply(msg) + case msg if self.hotswap.isEmpty && + defaultBehavior.isDefinedAt(msg) => defaultBehavior.apply(msg) + case unknown => unhandled(unknown) //This is the only line that differs from processingbehavior + } + actorBehavior + } +} + +private[actor] class AnyOptionAsTypedOption(anyOption: Option[Any]) { + + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will throw a ClassCastException + * if the actual type is not assignable from the given one. + */ + def as[T]: Option[T] = narrow[T](anyOption) + + /** + * Convenience helper to cast the given Option of Any to an Option of the given type. Will swallow a possible + * ClassCastException and return None in that case. + */ + def asSilently[T: Manifest]: Option[T] = narrowSilently[T](anyOption) +} + +/** + * Marker interface for proxyable actors (such as typed actor). + * + * @author Jonas Bonér + */ +trait Proxyable { + private[actor] def swapProxiedActor(newInstance: Actor) +} + +/** + * Represents the different Actor types. + * + * @author Jonas Bonér + */ +sealed trait ActorType +object ActorType { + case object ScalaActor extends ActorType + case object TypedActor extends ActorType +} diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala new file mode 100644 index 0000000000..79cf2cc920 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -0,0 +1,1536 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package akka.actor + +import akka.dispatch._ +import akka.config.Config._ +import akka.config.Supervision._ +import akka.AkkaException +import akka.util._ +import ReflectiveAccess._ + +import java.net.InetSocketAddress +import java.util.concurrent.atomic.{AtomicInteger, AtomicReference} +import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.{ ScheduledFuture, ConcurrentHashMap, TimeUnit } +import java.util.{ Map => JMap } +import java.lang.reflect.Field + +import scala.reflect.BeanProperty +import scala.collection.immutable.Stack +import scala.annotation.tailrec + +private[akka] object ActorRefInternals extends Logging { + + /** + * LifeCycles for ActorRefs. + */ + private[akka] sealed trait StatusType + object UNSTARTED extends StatusType + object RUNNING extends StatusType + object BEING_RESTARTED extends StatusType + object SHUTDOWN extends StatusType +} + + +/** + * ActorRef is an immutable and serializable handle to an Actor. + *

+ * Create an ActorRef for an Actor by using the factory method on the Actor object. + *

+ * Here is an example on how to create an actor with a default constructor. + *

+ *   import Actor._
+ *
+ *   val actor = actorOf[MyActor]
+ *   actor.start
+ *   actor ! message
+ *   actor.stop
+ * 
+ * + * You can also create and start actors like this: + *
+ *   val actor = actorOf[MyActor].start
+ * 
+ * + * Here is an example on how to create an actor with a non-default constructor. + *
+ *   import Actor._
+ *
+ *   val actor = actorOf(new MyActor(...))
+ *   actor.start
+ *   actor ! message
+ *   actor.stop
+ * 
+ * + * @author Jonas Bonér + */ +trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scalaRef: ScalaActorRef => + //Reuse same logger + import Actor.log + + // Only mutable for RemoteServer in order to maintain identity across nodes + @volatile + protected[akka] var _uuid = newUuid + @volatile + protected[this] var _status: ActorRefInternals.StatusType = ActorRefInternals.UNSTARTED + @volatile + protected[akka] var _homeAddress = new InetSocketAddress(RemoteServerModule.HOSTNAME, RemoteServerModule.PORT) + @volatile + protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None + protected[akka] val guard = new ReentrantGuard + + /** + * User overridable callback/setting. + *

+ * Identifier for actor, does not have to be a unique one. Default is the 'uuid'. + *

+ * This field is used for logging, AspectRegistry.actorsFor(id), identifier for remote + * actor in RemoteServer etc.But also as the identifier for persistence, which means + * that you can use a custom name to be able to retrieve the "correct" persisted state + * upon restart, remote restart etc. + */ + @BeanProperty + @volatile + var id: String = _uuid.toString + + /** + * User overridable callback/setting. + *

+ * Defines the default timeout for '!!' and '!!!' invocations, + * e.g. the timeout for the future returned by the call to '!!' and '!!!'. + */ + @BeanProperty + @volatile + var timeout: Long = Actor.TIMEOUT + + /** + * User overridable callback/setting. + *

+ * Defines the default timeout for an initial receive invocation. + * When specified, the receive function should be able to handle a 'ReceiveTimeout' message. + */ + @volatile + var receiveTimeout: Option[Long] = None + + /** + * Akka Java API + * Defines the default timeout for an initial receive invocation. + * When specified, the receive function should be able to handle a 'ReceiveTimeout' message. + */ + def setReceiveTimeout(timeout: Long) = this.receiveTimeout = Some(timeout) + def getReceiveTimeout(): Option[Long] = receiveTimeout + + /** + * Akka Java API + * A faultHandler defines what should be done when a linked actor signals an error. + *

+ * Can be one of: + *

+   * getContext().setFaultHandler(new AllForOneStrategy(new Class[]{Throwable.class},maxNrOfRetries, withinTimeRange));
+   * 
+ * Or: + *
+   * getContext().setFaultHandler(new OneForOneStrategy(new Class[]{Throwable.class},maxNrOfRetries, withinTimeRange));
+   * 
+ */ + def setFaultHandler(handler: FaultHandlingStrategy) + def getFaultHandler(): FaultHandlingStrategy + + @volatile + private[akka] var _dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher + + /** + * Akka Java API + * The default dispatcher is the Dispatchers.globalExecutorBasedEventDrivenDispatcher. + * This means that all actors will share the same event-driven executor based dispatcher. + *

+ * You can override it so it fits the specific use-case that the actor is used for. + * See the akka.dispatch.Dispatchers class for the different + * dispatchers available. + *

+ * The default is also that all actors that are created and spawned from within this actor + * is sharing the same dispatcher as its creator. + */ + def setDispatcher(dispatcher: MessageDispatcher) = this.dispatcher = dispatcher + def getDispatcher(): MessageDispatcher = dispatcher + + /** + * Holds the hot swapped partial function. + */ + @volatile + protected[akka] var hotswap = Stack[PartialFunction[Any, Unit]]() + + /** + * This is a reference to the message currently being processed by the actor + */ + @volatile + protected[akka] var currentMessage: MessageInvocation = null + + /** + * Comparison only takes uuid into account. + */ + def compareTo(other: ActorRef) = this.uuid compareTo other.uuid + + /** + * Returns the uuid for the actor. + */ + def getUuid() = _uuid + def uuid = _uuid + + /** + * Akka Java API + * The reference sender Actor of the last received message. + * Is defined if the message was sent from another Actor, else None. + */ + def getSender(): Option[ActorRef] = sender + + /** + * Akka Java API + * The reference sender future of the last received message. + * Is defined if the message was sent with sent with '!!' or '!!!', else None. + */ + def getSenderFuture(): Option[CompletableFuture[Any]] = senderFuture + + /** + * Is the actor being restarted? + */ + def isBeingRestarted: Boolean = _status == ActorRefInternals.BEING_RESTARTED + + /** + * Is the actor running? + */ + def isRunning: Boolean = _status match { + case ActorRefInternals.BEING_RESTARTED | ActorRefInternals.RUNNING => true + case _ => false + } + + /** + * Is the actor shut down? + */ + def isShutdown: Boolean = _status == ActorRefInternals.SHUTDOWN + + /** + * Is the actor ever started? + */ + def isUnstarted: Boolean = _status == ActorRefInternals.UNSTARTED + + /** + * Is the actor able to handle the message passed in as arguments? + */ + def isDefinedAt(message: Any): Boolean = actor.isDefinedAt(message) + + /** + * Only for internal use. UUID is effectively final. + */ + protected[akka] def uuid_=(uid: Uuid) = _uuid = uid + + /** + * Akka Java API + * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. + *

+ *

+   * actor.sendOneWay(message);
+   * 
+ *

+ */ + def sendOneWay(message: AnyRef): Unit = sendOneWay(message, null) + + /** + * Akka Java API + * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. + *

+ * Allows you to pass along the sender of the messag. + *

+ *

+   * actor.sendOneWay(message, context);
+   * 
+ *

+ */ + def sendOneWay(message: AnyRef, sender: ActorRef): Unit = this.!(message)(Option(sender)) + + /** + * Akka Java API + * @see sendRequestReply(message: AnyRef, timeout: Long, sender: ActorRef) + * Uses the defualt timeout of the Actor (setTimeout()) and omits the sender reference + */ + def sendRequestReply(message: AnyRef): AnyRef = sendRequestReply(message, timeout, null) + + /** + * Akka Java API + * @see sendRequestReply(message: AnyRef, timeout: Long, sender: ActorRef) + * Uses the defualt timeout of the Actor (setTimeout()) + */ + def sendRequestReply(message: AnyRef, sender: ActorRef): AnyRef = sendRequestReply(message, timeout, sender) + + /** + * Akka Java API + * Sends a message asynchronously and waits on a future for a reply message under the hood. + *

+ * It waits on the reply either until it receives it or until the timeout expires + * (which will throw an ActorTimeoutException). E.g. send-and-receive-eventually semantics. + *

+ * NOTE: + * Use this method with care. In most cases it is better to use 'sendOneWay' together with 'getContext().getSender()' to + * implement request/response message exchanges. + *

+ * If you are sending messages using sendRequestReply then you have to use getContext().reply(..) + * to send a reply message to the original sender. If not then the sender will block until the timeout expires. + */ + def sendRequestReply(message: AnyRef, timeout: Long, sender: ActorRef): AnyRef = { + !!(message, timeout)(Option(sender)).getOrElse(throw new ActorTimeoutException( + "Message [" + message + + "]\n\tsent to [" + actorClassName + + "]\n\tfrom [" + (if (sender ne null) sender.actorClassName else "nowhere") + + "]\n\twith timeout [" + timeout + + "]\n\ttimed out.")) + .asInstanceOf[AnyRef] + } + + /** + * Akka Java API + * @see sendRequestReplyFuture(message: AnyRef, sender: ActorRef): Future[_] + * Uses the Actors default timeout (setTimeout()) and omits the sender + */ + def sendRequestReplyFuture(message: AnyRef): Future[_] = sendRequestReplyFuture(message, timeout, null) + + /** + * Akka Java API + * @see sendRequestReplyFuture(message: AnyRef, sender: ActorRef): Future[_] + * Uses the Actors default timeout (setTimeout()) + */ + def sendRequestReplyFuture(message: AnyRef, sender: ActorRef): Future[_] = sendRequestReplyFuture(message, timeout, sender) + + /** + * Akka Java API + * Sends a message asynchronously returns a future holding the eventual reply message. + *

+ * NOTE: + * Use this method with care. In most cases it is better to use 'sendOneWay' together with the 'getContext().getSender()' to + * implement request/response message exchanges. + *

+ * If you are sending messages using sendRequestReplyFuture then you have to use getContext().reply(..) + * to send a reply message to the original sender. If not then the sender will block until the timeout expires. + */ + def sendRequestReplyFuture(message: AnyRef, timeout: Long, sender: ActorRef): Future[_] = !!!(message, timeout)(Option(sender)) + + /** + * Akka Java API + * Forwards the message specified to this actor and preserves the original sender of the message + */ + def forward(message: AnyRef, sender: ActorRef): Unit = + if (sender eq null) throw new IllegalArgumentException("The 'sender' argument to 'forward' can't be null") + else forward(message)(Some(sender)) + + /** + * Akka Java API + * Use getContext().replyUnsafe(..) to reply with a message to the original sender of the message currently + * being processed. + *

+ * Throws an IllegalStateException if unable to determine what to reply to. + */ + def replyUnsafe(message: AnyRef) = reply(message) + + /** + * Akka Java API + * Use getContext().replySafe(..) to reply with a message to the original sender of the message currently + * being processed. + *

+ * Returns true if reply was sent, and false if unable to determine what to reply to. + */ + def replySafe(message: AnyRef): Boolean = reply_?(message) + + /** + * Returns the class for the Actor instance that is managed by the ActorRef. + */ + def actorClass: Class[_ <: Actor] + + /** + * Akka Java API + * Returns the class for the Actor instance that is managed by the ActorRef. + */ + def getActorClass(): Class[_ <: Actor] = actorClass + + /** + * Returns the class name for the Actor instance that is managed by the ActorRef. + */ + def actorClassName: String + + /** + * Akka Java API + * Returns the class name for the Actor instance that is managed by the ActorRef. + */ + def getActorClassName(): String = actorClassName + + /** + * Sets the dispatcher for this actor. Needs to be invoked before the actor is started. + */ + def dispatcher_=(md: MessageDispatcher): Unit + + /** + * Get the dispatcher for this actor. + */ + def dispatcher: MessageDispatcher + + /** + * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. + */ + def makeRemote(hostname: String, port: Int): Unit + + /** + * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. + */ + def makeRemote(address: InetSocketAddress): Unit + + /** + * Returns the home address and port for this actor. + */ + def homeAddress: InetSocketAddress = _homeAddress + + /** + * Akka Java API + * Returns the home address and port for this actor. + */ + def getHomeAddress(): InetSocketAddress = homeAddress + + /** + * Set the home address and port for this actor. + */ + def homeAddress_=(hostnameAndPort: Tuple2[String, Int]): Unit = + homeAddress_=(new InetSocketAddress(hostnameAndPort._1, hostnameAndPort._2)) + + /** + * Akka Java API + * Set the home address and port for this actor. + */ + def setHomeAddress(hostname: String, port: Int): Unit = homeAddress = (hostname, port) + + /** + * Set the home address and port for this actor. + */ + def homeAddress_=(address: InetSocketAddress): Unit + + /** + * Akka Java API + * Set the home address and port for this actor. + */ + def setHomeAddress(address: InetSocketAddress): Unit = homeAddress = address + + /** + * Returns the remote address for the actor, if any, else None. + */ + def remoteAddress: Option[InetSocketAddress] + protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit + + /** + * Akka Java API + * Gets the remote address for the actor, if any, else None. + */ + def getRemoteAddress(): Option[InetSocketAddress] = remoteAddress + + /** + * Starts up the actor and its message queue. + */ + def start(): ActorRef + + /** + * Shuts down the actor its dispatcher and message queue. + * Alias for 'stop'. + */ + def exit() = stop() + + /** + * Shuts down the actor its dispatcher and message queue. + */ + def stop(): Unit + + /** + * Links an other actor to this actor. Links are unidirectional and means that a the linking actor will + * receive a notification if the linked actor has crashed. + *

+ * If the 'trapExit' member field of the 'faultHandler' has been set to at contain at least one exception class then it will + * 'trap' these exceptions and automatically restart the linked actors according to the restart strategy + * defined by the 'faultHandler'. + */ + def link(actorRef: ActorRef): Unit + + /** + * Unlink the actor. + */ + def unlink(actorRef: ActorRef): Unit + + /** + * Atomically start and link an actor. + */ + def startLink(actorRef: ActorRef): Unit + + /** + * Atomically start, link and make an actor remote. + */ + def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit + + /** + * Atomically create (from actor class) and start an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawn(clazz: Class[_ <: Actor]): ActorRef + + /** + * Atomically create (from actor class), make it remote and start an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef + + /** + * Atomically create (from actor class), link and start an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawnLink(clazz: Class[_ <: Actor]): ActorRef + + /** + * Atomically create (from actor class), make it remote, link and start an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef + + /** + * Returns the mailbox size. + */ + def mailboxSize = dispatcher.mailboxSize(this) + + /** + * Akka Java API + * Returns the mailbox size. + */ + def getMailboxSize(): Int = mailboxSize + + /** + * Returns the supervisor, if there is one. + */ + def supervisor: Option[ActorRef] + + /** + * Akka Java API + * Returns the supervisor, if there is one. + */ + def getSupervisor(): ActorRef = supervisor getOrElse null + + protected[akka] def invoke(messageHandle: MessageInvocation): Unit + + protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit + + protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( + message: Any, + timeout: Long, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] + + protected[akka] def actorInstance: AtomicReference[Actor] + + protected[akka] def actor: Actor = actorInstance.get + + protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit + + protected[akka] def mailbox: AnyRef + protected[akka] def mailbox_=(value: AnyRef): AnyRef + + protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit + + protected[akka] def restart(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit + + protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit + + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] + + protected[akka] def linkedActors: JMap[Uuid, ActorRef] + + override def hashCode: Int = HashCode.hash(HashCode.SEED, uuid) + + override def equals(that: Any): Boolean = { + that.isInstanceOf[ActorRef] && + that.asInstanceOf[ActorRef].uuid == uuid + } + + override def toString = "Actor[" + id + ":" + uuid + "]" + + protected[akka] def checkReceiveTimeout = { + cancelReceiveTimeout + if (receiveTimeout.isDefined && dispatcher.mailboxSize(this) <= 0) { //Only reschedule if desired and there are currently no more messages to be processed + log.debug("Scheduling timeout for %s", this) + _futureTimeout = Some(Scheduler.scheduleOnce(this, ReceiveTimeout, receiveTimeout.get, TimeUnit.MILLISECONDS)) + } + } + + protected[akka] def cancelReceiveTimeout = { + if (_futureTimeout.isDefined) { + _futureTimeout.get.cancel(true) + _futureTimeout = None + log.debug("Timeout canceled for %s", this) + } + } +} + +/** + * Local (serializable) ActorRef that is used when referencing the Actor on its "home" node. + * + * @author Jonas Bonér + */ +class LocalActorRef private[akka] ( + private[this] val actorFactory: () => Actor) + extends ActorRef with ScalaActorRef { + + @volatile + private[akka] var _remoteAddress: Option[InetSocketAddress] = None // only mutable to maintain identity across nodes + @volatile + private[akka] lazy val _linkedActors = new ConcurrentHashMap[Uuid, ActorRef] + @volatile + private[akka] var _supervisor: Option[ActorRef] = None + @volatile + private var maxNrOfRetriesCount: Int = 0 + @volatile + private var restartsWithinTimeRangeTimestamp: Long = 0L + @volatile + private var _mailbox: AnyRef = _ + + protected[akka] val actorInstance = guard.withGuard { new AtomicReference[Actor](newActor) } + + //If it was started inside "newActor", initialize it + if (isRunning) initializeActorInstance + + // used only for deserialization + private[akka] def this(__uuid: Uuid, + __id: String, + __hostname: String, + __port: Int, + __timeout: Long, + __receiveTimeout: Option[Long], + __lifeCycle: LifeCycle, + __supervisor: Option[ActorRef], + __hotswap: Stack[PartialFunction[Any, Unit]], + __factory: () => Actor) = { + this(__factory) + _uuid = __uuid + id = __id + homeAddress = (__hostname, __port) + timeout = __timeout + receiveTimeout = __receiveTimeout + lifeCycle = __lifeCycle + _supervisor = __supervisor + hotswap = __hotswap + setActorSelfFields(actor,this) + start + ActorRegistry.register(this) + } + + // ========= PUBLIC FUNCTIONS ========= + + /** + * Returns the class for the Actor instance that is managed by the ActorRef. + */ + def actorClass: Class[_ <: Actor] = actor.getClass.asInstanceOf[Class[_ <: Actor]] + + /** + * Returns the class name for the Actor instance that is managed by the ActorRef. + */ + def actorClassName: String = actorClass.getName + + /** + * Sets the dispatcher for this actor. Needs to be invoked before the actor is started. + */ + def dispatcher_=(md: MessageDispatcher): Unit = guard.withGuard { + if (!isBeingRestarted) { + if (!isRunning) _dispatcher = md + else throw new ActorInitializationException( + "Can not swap dispatcher for " + toString + " after it has been started") + } + } + + /** + * Get the dispatcher for this actor. + */ + def dispatcher: MessageDispatcher = _dispatcher + + /** + * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. + */ + def makeRemote(hostname: String, port: Int): Unit = { + ensureRemotingEnabled + if (!isRunning || isBeingRestarted) makeRemote(new InetSocketAddress(hostname, port)) + else throw new ActorInitializationException( + "Can't make a running actor remote. Make sure you call 'makeRemote' before 'start'.") + } + + /** + * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. + */ + def makeRemote(address: InetSocketAddress): Unit = guard.withGuard { + ensureRemotingEnabled + if (!isRunning || isBeingRestarted) { + _remoteAddress = Some(address) + RemoteClientModule.register(address, uuid) + homeAddress = (RemoteServerModule.HOSTNAME, RemoteServerModule.PORT) + } else throw new ActorInitializationException( + "Can't make a running actor remote. Make sure you call 'makeRemote' before 'start'.") + } + + /** + * Set the contact address for this actor. This is used for replying to messages + * sent asynchronously when no reply channel exists. + */ + def homeAddress_=(address: InetSocketAddress): Unit = _homeAddress = address + + /** + * Returns the remote address for the actor, if any, else None. + */ + def remoteAddress: Option[InetSocketAddress] = _remoteAddress + protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = _remoteAddress = addr + + /** + * Starts up the actor and its message queue. + */ + def start: ActorRef = guard.withGuard { + if (isShutdown) throw new ActorStartException( + "Can't restart an actor that has been shut down with 'stop' or 'exit'") + if (!isRunning) { + dispatcher.attach(this) + + _status = ActorRefInternals.RUNNING + + // If we are not currently creating this ActorRef instance + if ((actorInstance ne null) && (actorInstance.get ne null)) + initializeActorInstance + + checkReceiveTimeout //Schedule the initial Receive timeout + } + this + } + + /** + * Shuts down the actor its dispatcher and message queue. + */ + def stop() = guard.withGuard { + if (isRunning) { + receiveTimeout = None + cancelReceiveTimeout + dispatcher.detach(this) + _status = ActorRefInternals.SHUTDOWN + actor.postStop + ActorRegistry.unregister(this) + if (isRemotingEnabled) { + if (remoteAddress.isDefined) + RemoteClientModule.unregister(remoteAddress.get, uuid) + RemoteServerModule.unregister(this) + } + setActorSelfFields(actorInstance.get,null) + } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") + } + + /** + * Links an other actor to this actor. Links are unidirectional and means that a the linking actor will + * receive a notification if the linked actor has crashed. + *

+ * If the 'trapExit' member field of the 'faultHandler' has been set to at contain at least one exception class then it will + * 'trap' these exceptions and automatically restart the linked actors according to the restart strategy + * defined by the 'faultHandler'. + *

+ * To be invoked from within the actor itself. + */ + def link(actorRef: ActorRef) = guard.withGuard { + if (actorRef.supervisor.isDefined) throw new IllegalActorStateException( + "Actor can only have one supervisor [" + actorRef + "], e.g. link(actor) fails") + linkedActors.put(actorRef.uuid, actorRef) + actorRef.supervisor = Some(this) + Actor.log.debug("Linking actor [%s] to actor [%s]", actorRef, this) + } + + /** + * Unlink the actor. + *

+ * To be invoked from within the actor itself. + */ + def unlink(actorRef: ActorRef) = guard.withGuard { + if (!linkedActors.containsKey(actorRef.uuid)) throw new IllegalActorStateException( + "Actor [" + actorRef + "] is not a linked actor, can't unlink") + linkedActors.remove(actorRef.uuid) + actorRef.supervisor = None + Actor.log.debug("Unlinking actor [%s] from actor [%s]", actorRef, this) + } + + /** + * Atomically start and link an actor. + *

+ * To be invoked from within the actor itself. + */ + def startLink(actorRef: ActorRef): Unit = guard.withGuard { + link(actorRef) + actorRef.start + } + + /** + * Atomically start, link and make an actor remote. + *

+ * To be invoked from within the actor itself. + */ + def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = guard.withGuard { + ensureRemotingEnabled + actorRef.makeRemote(hostname, port) + link(actorRef) + actorRef.start + } + + /** + * Atomically create (from actor class) and start an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawn(clazz: Class[_ <: Actor]): ActorRef = guard.withGuard { + Actor.actorOf(clazz).start + } + + /** + * Atomically create (from actor class), start and make an actor remote. + *

+ * To be invoked from within the actor itself. + */ + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { + ensureRemotingEnabled + val actor = Actor.actorOf(clazz) + actor.makeRemote(hostname, port) + actor.start + actor + } + + /** + * Atomically create (from actor class), start and link an actor. + *

+ * To be invoked from within the actor itself. + */ + def spawnLink(clazz: Class[_ <: Actor]): ActorRef = guard.withGuard { + val actor = Actor.actorOf(clazz) + link(actor) + actor.start + actor + } + + /** + * Atomically create (from actor class), start, link and make an actor remote. + *

+ * To be invoked from within the actor itself. + */ + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { + ensureRemotingEnabled + val actor = Actor.actorOf(clazz) + actor.makeRemote(hostname, port) + link(actor) + actor.start + actor + } + + /** + * Returns the mailbox. + */ + def mailbox: AnyRef = _mailbox + + protected[akka] def mailbox_=(value: AnyRef): AnyRef = { _mailbox = value; value } + + /** + * Shuts down and removes all linked actors. + */ + def shutdownLinkedActors() { + val i = linkedActors.values.iterator + while(i.hasNext) { + i.next.stop + i.remove + } + } + + /** + * Returns the supervisor, if there is one. + */ + def supervisor: Option[ActorRef] = _supervisor + + // ========= AKKA PROTECTED FUNCTIONS ========= + + protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup + + protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = { + if (remoteAddress.isDefined && isRemotingEnabled) { + RemoteClientModule.send[Any]( + message, senderOption, None, remoteAddress.get, timeout, true, this, None, ActorType.ScalaActor) + } else { + val invocation = new MessageInvocation(this, message, senderOption, None) + dispatcher dispatchMessage invocation + } + } + + protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( + message: Any, + timeout: Long, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { + + if (remoteAddress.isDefined && isRemotingEnabled) { + val future = RemoteClientModule.send[T]( + message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, ActorType.ScalaActor) + if (future.isDefined) future.get + else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) + } else { + val future = if (senderFuture.isDefined) senderFuture else Some(new DefaultCompletableFuture[T](timeout)) + val invocation = new MessageInvocation( + this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) + dispatcher dispatchMessage invocation + future.get + } + } + + /** + * Callback for the dispatcher. This is the single entry point to the user Actor implementation. + */ + protected[akka] def invoke(messageHandle: MessageInvocation): Unit = guard.withGuard { + if (isShutdown) Actor.log.warning("Actor [%s] is shut down,\n\tignoring message [%s]", toString, messageHandle) + else { + currentMessage = messageHandle + try { + dispatch(messageHandle) + } catch { + case e => + Actor.log.error(e, "Could not invoke actor [%s]", this) + throw e + } finally { + currentMessage = null //TODO: Don't reset this, we might want to resend the message + } + } + } + + protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable) { + faultHandler match { + case AllForOneStrategy(trapExit,maxRetries, within) if trapExit.exists(_.isAssignableFrom(reason.getClass)) => + restartLinkedActors(reason, maxRetries, within) + + case OneForOneStrategy(trapExit,maxRetries, within) if trapExit.exists(_.isAssignableFrom(reason.getClass)) => + dead.restart(reason, maxRetries, within) + + case _ => + notifySupervisorWithMessage(Exit(this, reason)) + } + } + + private def requestRestartPermission(maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Boolean = { + val denied = if (maxNrOfRetries.isEmpty && withinTimeRange.isEmpty) { //Immortal + false + } else if (withinTimeRange.isEmpty) { // restrict number of restarts + maxNrOfRetriesCount += 1 //Increment number of retries + maxNrOfRetriesCount > maxNrOfRetries.get + } else { // cannot restart more than N within M timerange + maxNrOfRetriesCount += 1 //Increment number of retries + val windowStart = restartsWithinTimeRangeTimestamp + val now = System.currentTimeMillis + val retries = maxNrOfRetriesCount + //We are within the time window if it isn't the first restart, or if the window hasn't closed + val insideWindow = if (windowStart == 0) false + else (now - windowStart) <= withinTimeRange.get + + //The actor is dead if it dies X times within the window of restart + val unrestartable = insideWindow && retries > maxNrOfRetries.getOrElse(1) + + if (windowStart == 0 || !insideWindow) //(Re-)set the start of the window + restartsWithinTimeRangeTimestamp = now + + if (windowStart != 0 && !insideWindow) //Reset number of restarts if window has expired + maxNrOfRetriesCount = 1 + + unrestartable + } + + denied == false //If we weren't denied, we have a go + } + + protected[akka] def restart(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]) { + + def performRestart { + Actor.log.info("Restarting actor [%s] configured as PERMANENT.", id) + val failedActor = actorInstance.get + Actor.log.debug("Invoking 'preRestart' for failed actor instance [%s].", id) + failedActor.preRestart(reason) + val freshActor = newActor + setActorSelfFields(failedActor,null) //Only null out the references if we could instantiate the new actor + actorInstance.set(freshActor) //Assign it here so if preStart fails, we can null out the sef-refs next call + freshActor.preStart + failedActor match { + case p: Proxyable => p.swapProxiedActor(freshActor) + case _ => + } + Actor.log.debug("Invoking 'postRestart' for new actor instance [%s].", id) + freshActor.postRestart(reason) + } + + def tooManyRestarts { + Actor.log.warning( + "Maximum number of restarts [%s] within time range [%s] reached." + + "\n\tWill *not* restart actor [%s] anymore." + + "\n\tLast exception causing restart was" + + "\n\t[%s].", + maxNrOfRetries, withinTimeRange, this, reason) + _supervisor.foreach { sup => + // can supervisor handle the notification? + val notification = MaximumNumberOfRestartsWithinTimeRangeReached(this, maxNrOfRetries, withinTimeRange, reason) + if (sup.isDefinedAt(notification)) notifySupervisorWithMessage(notification) + else Actor.log.warning( + "No message handler defined for system message [MaximumNumberOfRestartsWithinTimeRangeReached]" + + "\n\tCan't send the message to the supervisor [%s].", sup) + } + + stop + } + + @tailrec def attemptRestart() { + val success = if (requestRestartPermission(maxNrOfRetries,withinTimeRange)) { + guard.withGuard[Boolean] { + _status = ActorRefInternals.BEING_RESTARTED + + lifeCycle match { + case Temporary => + shutDownTemporaryActor(this) + true + case _ => // either permanent or none where default is permanent + val success = try { + performRestart + true + } catch { + case e => false //An error or exception here should trigger a retry + } + + Actor.log.debug("Restart: %s for [%s].", success, id) + + if (success) { + _status = ActorRefInternals.RUNNING + dispatcher.resume(this) + restartLinkedActors(reason,maxNrOfRetries,withinTimeRange) + } + + success + } + } + } else { + tooManyRestarts + true //Done + } + + if (success) + () //Alles gut + else + attemptRestart + } + + attemptRestart() //Tailrecursion + } + + protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]) = { + import scala.collection.JavaConversions._ + linkedActors.values foreach { actorRef => + actorRef.lifeCycle match { + // either permanent or none where default is permanent + case Temporary => shutDownTemporaryActor(actorRef) + case _ => actorRef.restart(reason, maxNrOfRetries, withinTimeRange) + } + } + } + + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { + ensureRemotingEnabled + if (_supervisor.isDefined) { + remoteAddress.foreach(address => RemoteClientModule.registerSupervisorForActor(address, this)) + Some(_supervisor.get.uuid) + } else None + } + + protected[akka] def linkedActors: JMap[Uuid, ActorRef] = _linkedActors + + // ========= PRIVATE FUNCTIONS ========= + + private[this] def newActor: Actor = { + val a = Actor.actorRefInCreation.withValue(Some(this)) { actorFactory() } + if (a eq null) throw new ActorInitializationException("Actor instance passed to ActorRef can not be 'null'") + a + } + + private def dispatch[T](messageHandle: MessageInvocation) = { + Actor.log.trace("Invoking actor with message: %s\n", messageHandle) + val message = messageHandle.message //serializeMessage(messageHandle.message) + + try { + cancelReceiveTimeout // FIXME: leave this here? + actor(message) + } catch { + case e: InterruptedException => {} // received message while actor is shutting down, ignore + case e => handleExceptionInDispatch(e, message) + } + finally { + checkReceiveTimeout // Reschedule receive timeout + } + } + + private def shutDownTemporaryActor(temporaryActor: ActorRef) { + Actor.log.info("Actor [%s] configured as TEMPORARY and will not be restarted.", temporaryActor.id) + temporaryActor.stop + linkedActors.remove(temporaryActor.uuid) // remove the temporary actor + // if last temporary actor is gone, then unlink me from supervisor + if (linkedActors.isEmpty) { + Actor.log.info( + "All linked actors have died permanently (they were all configured as TEMPORARY)" + + "\n\tshutting down and unlinking supervisor actor as well [%s].", + temporaryActor.id) + notifySupervisorWithMessage(UnlinkAndStop(this)) + } + + true + } + + private def handleExceptionInDispatch(reason: Throwable, message: Any) = { + Actor.log.error(reason, "Exception when invoking \n\tactor [%s] \n\twith message [%s]", this, message) + + //Prevent any further messages to be processed until the actor has been restarted + dispatcher.suspend(this) + + senderFuture.foreach(_.completeWithException(reason)) + + if (supervisor.isDefined) notifySupervisorWithMessage(Exit(this, reason)) + else { + lifeCycle match { + case Temporary => shutDownTemporaryActor(this) + case _ => dispatcher.resume(this) //Resume processing for this actor + } + } + } + + private def notifySupervisorWithMessage(notification: LifeCycleMessage) = { + // FIXME to fix supervisor restart of remote actor for oneway calls, inject a supervisor proxy that can send notification back to client + _supervisor.foreach { sup => + if (sup.isShutdown) { // if supervisor is shut down, game over for all linked actors + shutdownLinkedActors + stop + } else sup ! notification // else notify supervisor + } + } + + private def setActorSelfFields(actor: Actor, value: ActorRef) { + + @tailrec def lookupAndSetSelfFields(clazz: Class[_],actor: Actor, value: ActorRef): Boolean = { + val success = try { + val selfField = clazz.getDeclaredField("self") + val someSelfField = clazz.getDeclaredField("someSelf") + selfField.setAccessible(true) + someSelfField.setAccessible(true) + selfField.set(actor,value) + someSelfField.set(actor, if (value ne null) Some(value) else null) + true + } catch { + case e: NoSuchFieldException => false + } + + if (success) { + true + } + else { + val parent = clazz.getSuperclass + if (parent eq null) + throw new IllegalActorStateException(toString + " is not an Actor since it have not mixed in the 'Actor' trait") + lookupAndSetSelfFields(parent,actor,value) + } + } + + lookupAndSetSelfFields(actor.getClass,actor,value) + } + + private def initializeActorInstance = { + actor.preStart // run actor preStart + Actor.log.trace("[%s] has started", toString) + ActorRegistry.register(this) + } + + /* + private def serializeMessage(message: AnyRef): AnyRef = if (Actor.SERIALIZE_MESSAGES) { + if (!message.isInstanceOf[String] && + !message.isInstanceOf[Byte] && + !message.isInstanceOf[Int] && + !message.isInstanceOf[Long] && + !message.isInstanceOf[Float] && + !message.isInstanceOf[Double] && + !message.isInstanceOf[Boolean] && + !message.isInstanceOf[Char] && + !message.isInstanceOf[Tuple2[_, _]] && + !message.isInstanceOf[Tuple3[_, _, _]] && + !message.isInstanceOf[Tuple4[_, _, _, _]] && + !message.isInstanceOf[Tuple5[_, _, _, _, _]] && + !message.isInstanceOf[Tuple6[_, _, _, _, _, _]] && + !message.isInstanceOf[Tuple7[_, _, _, _, _, _, _]] && + !message.isInstanceOf[Tuple8[_, _, _, _, _, _, _, _]] && + !message.getClass.isArray && + !message.isInstanceOf[List[_]] && + !message.isInstanceOf[scala.collection.immutable.Map[_, _]] && + !message.isInstanceOf[scala.collection.immutable.Set[_]]) { + Serializer.Java.deepClone(message) + } else message + } else message + */ +} + +/** + * System messages for RemoteActorRef. + * + * @author Jonas Bonér + */ +object RemoteActorSystemMessage { + val Stop = "RemoteActorRef:stop".intern +} + +/** + * Remote ActorRef that is used when referencing the Actor on a different node than its "home" node. + * This reference is network-aware (remembers its origin) and immutable. + * + * @author Jonas Bonér + */ +private[akka] case class RemoteActorRef private[akka] ( + classOrServiceName: String, + val actorClassName: String, + val hostname: String, + val port: Int, + _timeout: Long, + loader: Option[ClassLoader], + val actorType: ActorType = ActorType.ScalaActor) + extends ActorRef with ScalaActorRef { + + ensureRemotingEnabled + + id = classOrServiceName + timeout = _timeout + + start + + def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = + RemoteClientModule.send[Any]( + message, senderOption, None, remoteAddress.get, timeout, true, this, None, actorType) + + def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( + message: Any, + timeout: Long, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { + val future = RemoteClientModule.send[T]( + message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, actorType) + if (future.isDefined) future.get + else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) + } + + def start: ActorRef = synchronized { + _status = ActorRefInternals.RUNNING + this + } + + def stop: Unit = synchronized { + if (_status == ActorRefInternals.RUNNING) { + _status = ActorRefInternals.SHUTDOWN + postMessageToMailbox(RemoteActorSystemMessage.Stop, None) + } + } + + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None + + val remoteAddress: Option[InetSocketAddress] = Some(new InetSocketAddress(hostname, port)) + + // ==== NOT SUPPORTED ==== + def actorClass: Class[_ <: Actor] = unsupported + def dispatcher_=(md: MessageDispatcher): Unit = unsupported + def dispatcher: MessageDispatcher = unsupported + def makeRemote(hostname: String, port: Int): Unit = unsupported + def makeRemote(address: InetSocketAddress): Unit = unsupported + def homeAddress_=(address: InetSocketAddress): Unit = unsupported + def link(actorRef: ActorRef): Unit = unsupported + def unlink(actorRef: ActorRef): Unit = unsupported + def startLink(actorRef: ActorRef): Unit = unsupported + def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = unsupported + def spawn(clazz: Class[_ <: Actor]): ActorRef = unsupported + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported + def spawnLink(clazz: Class[_ <: Actor]): ActorRef = unsupported + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported + def supervisor: Option[ActorRef] = unsupported + def shutdownLinkedActors: Unit = unsupported + protected[akka] def mailbox: AnyRef = unsupported + protected[akka] def mailbox_=(value: AnyRef): AnyRef = unsupported + protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = unsupported + protected[akka] def restart(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit = unsupported + protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit = unsupported + protected[akka] def linkedActors: JMap[Uuid, ActorRef] = unsupported + protected[akka] def invoke(messageHandle: MessageInvocation): Unit = unsupported + protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = unsupported + protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = unsupported + protected[akka] def actorInstance: AtomicReference[Actor] = unsupported + private def unsupported = throw new UnsupportedOperationException("Not supported for RemoteActorRef") +} + +/** + * This trait represents the common (external) methods for all ActorRefs + * Needed because implicit conversions aren't applied when instance imports are used + * + * i.e. + * var self: ScalaActorRef = ... + * import self._ + * //can't call ActorRef methods here unless they are declared in a common + * //superclass, which ActorRefShared is. + */ +trait ActorRefShared { + /** + * Returns the uuid for the actor. + */ + def uuid: Uuid + + /** + * Shuts down and removes all linked actors. + */ + def shutdownLinkedActors(): Unit +} + +/** + * This trait represents the Scala Actor API + * There are implicit conversions in ../actor/Implicits.scala + * from ActorRef -> ScalaActorRef and back + */ +trait ScalaActorRef extends ActorRefShared { ref: ActorRef => + + /** + * Identifier for actor, does not have to be a unique one. Default is the 'uuid'. + *

+ * This field is used for logging, AspectRegistry.actorsFor(id), identifier for remote + * actor in RemoteServer etc.But also as the identifier for persistence, which means + * that you can use a custom name to be able to retrieve the "correct" persisted state + * upon restart, remote restart etc. + */ + def id: String + + def id_=(id: String): Unit + + /** + * User overridable callback/setting. + *

+ * Defines the life-cycle for a supervised actor. + */ + @volatile + @BeanProperty + var lifeCycle: LifeCycle = UndefinedLifeCycle + + /** + * User overridable callback/setting. + *

+ * Don't forget to supply a List of exception types to intercept (trapExit) + *

+ * Can be one of: + *

+   *  faultHandler = AllForOneStrategy(trapExit = List(classOf[Exception]),maxNrOfRetries, withinTimeRange)
+   * 
+ * Or: + *
+   *  faultHandler = OneForOneStrategy(trapExit = List(classOf[Exception]),maxNrOfRetries, withinTimeRange)
+   * 
+ */ + @volatile + @BeanProperty + var faultHandler: FaultHandlingStrategy = NoFaultHandlingStrategy + + /** + * The reference sender Actor of the last received message. + * Is defined if the message was sent from another Actor, else None. + */ + def sender: Option[ActorRef] = { + val msg = currentMessage + if (msg eq null) None + else msg.sender + } + + /** + * The reference sender future of the last received message. + * Is defined if the message was sent with sent with '!!' or '!!!', else None. + */ + def senderFuture(): Option[CompletableFuture[Any]] = { + val msg = currentMessage + if (msg eq null) None + else msg.senderFuture + } + + /** + * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. + *

+ * + * If invoked from within an actor then the actor reference is implicitly passed on as the implicit 'sender' argument. + *

+ * + * This actor 'sender' reference is then available in the receiving actor in the 'sender' member variable, + * if invoked from within an Actor. If not then no sender is available. + *

+   *   actor ! message
+   * 
+ *

+ */ + def !(message: Any)(implicit sender: Option[ActorRef] = None): Unit = { + if (isRunning) postMessageToMailbox(message, sender) + else throw new ActorInitializationException( + "Actor has not been started, you need to invoke 'actor.start' before using it") + } + + /** + * Sends a message asynchronously and waits on a future for a reply message. + *

+ * It waits on the reply either until it receives it (in the form of Some(replyMessage)) + * or until the timeout expires (which will return None). E.g. send-and-receive-eventually semantics. + *

+ * NOTE: + * Use this method with care. In most cases it is better to use '!' together with the 'sender' member field to + * implement request/response message exchanges. + * If you are sending messages using !! then you have to use self.reply(..) + * to send a reply message to the original sender. If not then the sender will block until the timeout expires. + */ + def !!(message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Option[Any] = { + if (isRunning) { + val future = postMessageToMailboxAndCreateFutureResultWithTimeout[Any](message, timeout, sender, None) + val isMessageJoinPoint = if (isTypedActorEnabled) TypedActorModule.resolveFutureIfMessageIsJoinPoint(message, future) + else false + try { + future.await + } catch { + case e: FutureTimeoutException => + if (isMessageJoinPoint) throw e + else None + } + if (future.exception.isDefined) throw future.exception.get + else future.result + } else throw new ActorInitializationException( + "Actor has not been started, you need to invoke 'actor.start' before using it") + } + + /** + * Sends a message asynchronously returns a future holding the eventual reply message. + *

+ * NOTE: + * Use this method with care. In most cases it is better to use '!' together with the 'sender' member field to + * implement request/response message exchanges. + * If you are sending messages using !!! then you have to use self.reply(..) + * to send a reply message to the original sender. If not then the sender will block until the timeout expires. + */ + def !!![T](message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Future[T] = { + if (isRunning) postMessageToMailboxAndCreateFutureResultWithTimeout[T](message, timeout, sender, None) + else throw new ActorInitializationException( + "Actor has not been started, you need to invoke 'actor.start' before using it") + } + + /** + * Forwards the message and passes the original sender actor as the sender. + *

+ * Works with '!', '!!' and '!!!'. + */ + def forward(message: Any)(implicit sender: Some[ActorRef]) = { + if (isRunning) { + if (sender.get.senderFuture.isDefined) postMessageToMailboxAndCreateFutureResultWithTimeout( + message, timeout, sender.get.sender, sender.get.senderFuture) + else if (sender.get.sender.isDefined) postMessageToMailbox(message, sender.get.sender) + else throw new IllegalActorStateException("Can't forward message when initial sender is not an actor") + } else throw new ActorInitializationException("Actor has not been started, you need to invoke 'actor.start' before using it") + } + + /** + * Use self.reply(..) to reply with a message to the original sender of the message currently + * being processed. + *

+ * Throws an IllegalStateException if unable to determine what to reply to. + */ + def reply(message: Any) = if (!reply_?(message)) throw new IllegalActorStateException( + "\n\tNo sender in scope, can't reply. " + + "\n\tYou have probably: " + + "\n\t\t1. Sent a message to an Actor from an instance that is NOT an Actor." + + "\n\t\t2. Invoked a method on an TypedActor from an instance NOT an TypedActor." + + "\n\tElse you might want to use 'reply_?' which returns Boolean(true) if succes and Boolean(false) if no sender in scope") + + /** + * Use reply_?(..) to reply with a message to the original sender of the message currently + * being processed. + *

+ * Returns true if reply was sent, and false if unable to determine what to reply to. + */ + def reply_?(message: Any): Boolean = { + if (senderFuture.isDefined) { + senderFuture.get completeWithResult message + true + } else if (sender.isDefined) { + //TODO: optimize away this allocation, perhaps by having implicit self: Option[ActorRef] in signature + sender.get.!(message)(Some(this)) + true + } else false + } + + /** + * Abstraction for unification of sender and senderFuture for later reply + */ + def channel: Channel[Any] = { + if (senderFuture.isDefined) { + new Channel[Any] { + val future = senderFuture.get + def !(msg: Any) = future completeWithResult msg + } + } else if (sender.isDefined) { + val someSelf = Some(this) + new Channel[Any] { + val client = sender.get + def !(msg: Any) = client.!(msg)(someSelf) + } + } else throw new IllegalActorStateException("No channel available") + } + + /** + * Atomically create (from actor class) and start an actor. + */ + def spawn[T <: Actor: Manifest]: ActorRef = + spawn(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + + /** + * Atomically create (from actor class), start and make an actor remote. + */ + def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = { + ensureRemotingEnabled + spawnRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port) + } + + /** + * Atomically create (from actor class), start and link an actor. + */ + def spawnLink[T <: Actor: Manifest]: ActorRef = + spawnLink(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + + /** + * Atomically create (from actor class), start, link and make an actor remote. + */ + def spawnLinkRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = { + ensureRemotingEnabled + spawnLinkRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port) + } +} + +/** + * Abstraction for unification of sender and senderFuture for later reply + */ +abstract class Channel[T] { + /** + * Sends the specified message to the channel + * Scala API + */ + def !(msg: T): Unit + + /** + * Sends the specified message to the channel + * Java API + */ + def sendOneWay(msg: T): Unit = this.!(msg) +} diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala new file mode 100644 index 0000000000..bf0a479f7f --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -0,0 +1,438 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package akka.actor + +import scala.collection.mutable.{ListBuffer, Map} +import scala.reflect.Manifest + +import java.util.concurrent.{ConcurrentSkipListSet, ConcurrentHashMap} +import java.util.{Set => JSet} + +import annotation.tailrec +import akka.util.ReflectiveAccess._ +import akka.util.{ReadWriteGuard, Address, ListenerManagement} +import java.net.InetSocketAddress + +/** + * Base trait for ActorRegistry events, allows listen to when an actor is added and removed from the ActorRegistry. + * + * @author Jonas Bonér + */ +sealed trait ActorRegistryEvent +case class ActorRegistered(actor: ActorRef) extends ActorRegistryEvent +case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent + +/** + * Registry holding all Actor instances in the whole system. + * Mapped by: + *